From 5eebe2e6ffa5daf9f794a6a1bec5fba17523cbbd Mon Sep 17 00:00:00 2001 From: Seraphym-Tuhai Date: Fri, 6 Jan 2023 12:02:44 +0200 Subject: [PATCH 001/168] add guide for run tests on built UI --- msa/black-box-tests/README.md | 8 +++++--- .../server/msa/TestProperties.java | 19 +++---------------- .../src/test/resources/config.properties | 2 -- 3 files changed, 8 insertions(+), 21 deletions(-) delete mode 100644 msa/black-box-tests/src/test/resources/config.properties diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md index ae0e8b79a0..df28271864 100644 --- a/msa/black-box-tests/README.md +++ b/msa/black-box-tests/README.md @@ -42,6 +42,8 @@ As result, in REPOSITORY column, next images should be present: mvn clean install -DblackBoxTests.skip=false -Dsuite=all - - - +### To run a separate test manually on a built UI: +1. Add the black-box-tests module in the [pom.xml](../pom.xml) +2. Add Vm Option "-DrunLocal=true -Dtb.baseUiUrl=http://localhost:4200/" in Run -> Edit Configuration -> Edit Configuration Templates -> TestNG +3. Go to the test class you need in the [UI tests directory](../black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests) and run the test in it +or go to the [resources](../black-box-tests/src/test/resources) and run test suite you need \ No newline at end of file diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java index b5d91647c1..e5ccc62f4c 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java @@ -36,33 +36,20 @@ public class TestProperties { if (instance.isActive()) { return HTTPS_URL; } - return getProperties().getProperty("tb.baseUrl"); + return System.getProperty("tb.baseUrl", "http://localhost:8080"); } public static String getBaseUiUrl() { if (instance.isActive()) { return "https://host.docker.internal"; } - return getProperties().getProperty("tb.baseUrl"); + return System.getProperty("tb.baseUiUrl", "http://localhost:8080"); } public static String getWebSocketUrl() { if (instance.isActive()) { return WSS_URL; } - return getProperties().getProperty("tb.wsUrl"); + return System.getProperty("tb.wsUrl", "ws://localhost:8080"); } - - private static Properties getProperties() { - if (properties == null) { - try (InputStream input = TestProperties.class.getClassLoader().getResourceAsStream("config.properties")) { - properties = new Properties(); - properties.load(input); - } catch (IOException ex) { - log.error("Exception while reading test properties " + ex.getMessage()); - } - } - return properties; - } - } diff --git a/msa/black-box-tests/src/test/resources/config.properties b/msa/black-box-tests/src/test/resources/config.properties deleted file mode 100644 index 419c73185d..0000000000 --- a/msa/black-box-tests/src/test/resources/config.properties +++ /dev/null @@ -1,2 +0,0 @@ -tb.baseUrl=http://localhost:8080 -tb.wsUrl=ws://localhost:8080 From 166c543faf14b8a1db3198f0acf6628b1f49c056 Mon Sep 17 00:00:00 2001 From: Seraphym-Tuhai Date: Wed, 11 Jan 2023 13:24:30 +0200 Subject: [PATCH 002/168] add one more step in README.md --- msa/black-box-tests/README.md | 3 ++- .../src/test/resources/addAsMavenProject.png | Bin 0 -> 125747 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 msa/black-box-tests/src/test/resources/addAsMavenProject.png diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md index df28271864..1907991dd3 100644 --- a/msa/black-box-tests/README.md +++ b/msa/black-box-tests/README.md @@ -43,7 +43,8 @@ As result, in REPOSITORY column, next images should be present: mvn clean install -DblackBoxTests.skip=false -Dsuite=all ### To run a separate test manually on a built UI: -1. Add the black-box-tests module in the [pom.xml](../pom.xml) +1. Add the black-box-tests module in the [pom.xml](../pom.xml) or add as a Maven project as in the picture +![addAsMavenProject.png](../black-box-tests/src/test/resources/addAsMavenProject.png) 2. Add Vm Option "-DrunLocal=true -Dtb.baseUiUrl=http://localhost:4200/" in Run -> Edit Configuration -> Edit Configuration Templates -> TestNG 3. Go to the test class you need in the [UI tests directory](../black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests) and run the test in it or go to the [resources](../black-box-tests/src/test/resources) and run test suite you need \ No newline at end of file diff --git a/msa/black-box-tests/src/test/resources/addAsMavenProject.png b/msa/black-box-tests/src/test/resources/addAsMavenProject.png new file mode 100644 index 0000000000000000000000000000000000000000..0f5e9bbb3a4515ddc5b5233ced129b6e384071c6 GIT binary patch literal 125747 zcmaI71yoyKn=c&Pio08j7ccJa?(XjHP^7pNcXxMp3KVxJ?h@QxZu+11ojdb=GdF8x zB_#VC&N|uqd49IT739Pb;ql-B005$-goqLV08Rh^K(xbxgFex&#O;ANZR0jn`aE(SQ1%72x zW7U#->}rVPf?NHfkOv z?%W()y)Kt%2NOrHd~adZ%Hqn)Aa?bmn+O=dRC>+@yN`18k`CwT<&{H@B$y)DqqR5? zMd%1uOx7I?MF^Icm$8&2808x06AdhBFb{MPybvsDtYJCER|q6H$UsYK(BFBKgb4$? z$b6&NVV(LI!6-uHEWnO85im%=nm;Ux5Fh{rAOt%t@cT9yC8Gy)PR~!!n4nQaL4r`E z!6X`JjO(HvQU(fwu%vrTI4XY!L=9snioo(tc<|^+NsUI~*6je802UpMuC8}2_M2=O z6T4D}l=fZUj=vGV-nXFB?PH+BK>nIc1q0lBKbdD^?Gi2;b(e9IJz6G{6jhevesvcGK4v#3kKCB_{4MyTikZoSB^+g*O8W7_`x> z_m~MxgbFEvx3;n>Zt)PxbI=z2u0VCln?DDVXT#lk}0!ojx@zE1EI>y(|9X&R1YIv2!`|mN;95xDRE!G0P!0l);97Iy+m@d-n^(eUrN^Jl!!-}mBQBgi@6G0`udFW<6i>D%;J_O;!%DfJbo#-JiS+Tq?in-S|3P-{<$ax)1=c3_Rbs-4z3Q z0`^(UX(!M5wcr3M5{oAv@lb&30~Y@k)ylhQ^H0G(ileVjbXdE4Wy%m_5gDMF;dwk? z!L2Ws#{aR>WW6huHfU5eH5M5i4F&iq;d1)u*x{>$Ja^u@Dm^<35THnC zi*0Oafe28cF>P2r4+4ESdY-g=h9qx#Ea`GML9Xv{_>_Tb$C5$m&R(>5O0Zp7aam(5 z8SM7wl-__D7qZpx>PpFX=4ZkkbiXO^8>>Pd5NdOBZ&8=CsbzsCy^YmF^pg_Mq@D>XKCpawN-BuffrTK>)I@{vDj&AU_|ex|J;G>>{d1+yRoq` zXvMlcP8)XhF{aIWb)pY$J8X2j(tiCKolgbeaC^-tFP8#HNTfFL*%Ja^KRC zO5vg+CXAl0JQ`&;ZK3QObVg;;SOtEYbGugUtJ{6!eI8=kTeg37W&?20#J)ZB(KsH& z5QZb_`}t@B!2vtO4IJiAow%fajd@+~i~G)lvSN4u0Y>XVXJDP6kv|`T&2!B2$>HRh z0@6A8QI1Cu1WtbImt|aMvcmUI2hsdKp4RoYHt6D15rTDZpRbpJV>_Dd+D$Z+uwWJ) z#bxDrZHI>gk{an)v#QU_>gq{y01Fk4;7xx%V?T$%B)^w%=bdDFcq_h#VP$t$R)NTf z_+fj=Ok9VzW>#ZK3-=N87X}*Qk6v7W0JrQ)OiT=%4Gj?q{Gaz7H~#lt)43>$xon09 z7uQ`2l-ou)6ex+HqAua)XR*ZY-Bz3Ct8ZRMh(D(IH+i3SWbZVX(~Bb`BFV|f$@yi$Y`gre^?Z>j4VG@umm%vUy_+#N0I$ec`xcEjFfSPdiE(5U}i2JpDbp z*FTkQk&-#=9M#TmmeX}9PoUpbU_MVq{Pr;%qtl%U83^H`zbOgD8+wnvq!YVMC;phj z0{~V`w$)J-7ctMG2(tlVDa!S9$D5?Hj{chRv1Pr&Nj1i3G@ zBBMl*apmzVqhSp(@%xlsZ;^|ryD#Mhg~G#MduOPD^_n#ha(Td#!wXqRwOOp#HMH3$ z$vL}+h5!Uo&h_>y)2Q=|%Bv@QYn*YK&az^yl6}uBf?145cqGNIs_pHpEdqwHzH&R5 zuk_=787{8`02)>2be0NT1eFT}F>b0jSqh_mut7&kq5^UW&zncO!_nq&Z4|1p{oWIO<+}ZgwxX>yI+? zTS*y+cYb@6uBeK~5G{9FA_NJ71F17Jq?E?TZJA&3Gg9MmGu@vgB+&Eyc48s8IF+a^VHtxj7 zGa<2FDtw1y7vN)I)4`&{y3*hwHFSyQ%T0+7{!x4PZDO4l%GEMM>sGTa53O8kx#zS| zZuM;Esx9h|>gJUE9vZ+wh46X(>^odUQ6F8*Q5$h*0~R3Qsb9XCYBB{|UK$})wnV8p zdL?6fwy|kBS!7587kM=kAn^EehBUWrX0b<1%z9;&@HCD@?jh@-TOZM}xrN)~=S=Lz zhI7-vIf)LvIdh~4I~yA&R<_T1n=3#;!=t{g&Jr!0bZ_qX_&9}54}%2j!L6bd-@(g1 z2F%D&#KU9cZr1Tj1v}3D>GJBTqQ1U9|5JI=xbG&#>OPpxY3#j;bK%`M1_UQbtSH*6 zU^r)i1xyzsQSw_EI6>+>!-TxlG_?T&;B@Hi(^EEyqni#^?(Ws+*zK70JIiWe#&y#1 zFG{(bI@T91yoI&PBkkR~JNKS^4i_>I@$iF)soWOVD`&LIBkbwce>N8dbUQWFjSbyC zw0z^Wn59{*!gZcy$%x1xcqcO-TmNwmr=m+kL;K|CcaBZ;;)Qz6+j6m{8CP#`cDxW2 ziU9!#>eJ}(YFa<<^gW#PP;Op53+;JdQ5KHap23i~Jc={wSXWfiS#y_gwCAW>wmByq z{4zMGNAxVpp~HuxMbzxd5rAT@e!pO1!| zbbm3>V3mD{c|CiD@XgWZeUSFPdE)hFF*PqS>4_UEA`o+xF1vP%UIbcn*B;s*oSx2O zYR|P9oY_(H>1-5Ma=GWD)2q*?RA5M7}X8KYt*wa#78tAmSzPK4HCwO1)rY~6M z=acoV>o}Izx?dNFGvi^3!X}KEp=OL&eG5{-!aP>VAWX~&knM#90O)14Lyfm98&&qt zkWik}tjCgKH|4agrOqw%q`cobSNCD8&@xloSW6pjZMs}jPoeQLTl1fv-x|*%+3j2~ zlc-(BKYTu{_aTS)?j$V10gk=W4V+cb+S z{$yjtAhyQw!L{|!@Hw3W8}6W$_tW!qdKDEvT2@h3cE0^n_pM^7v;Ag2Ia%$Y4+OIY z2KL=LoOR}ahE_w?h#JL%Z}ReknCZnJ90ev&v&7Ci-w`AwEZn760|R)VzA#`;d-tK= zcSx5xex~?XX4y#|hex^OwE59ouqp!qu&r)&;WDyaypF#5wX)#QRAsLK=Fq6e>Cl^< z&}AabOdNIQDhe}&Nj?0kz{}w%YQ8(z1tYVZCfTMvyZqVmyc5jckOkLxP0oXB>79%~ zQbHlX$8h6VV=#}Opz_y(8p`#L5?8?y14)5Uvr1w15=TMF%}qHEB|%dzru^)ej8Pwwr{;0!^uhAdpkqC_CAd-|$5Ydc}S%gimR(8fgO-LF% zy%#6UKl!%goaO7S;vmK=1p7p>d7*4r+|$Opz4M{w;ZUP1f7GE;G-y&;<}p35aCjz! z!vB;Rd}Q$byl>wdeR+h4G&q}6MOs1Q2qTm?PodTHhJw%E=OyZX=lU&P8GCp_?$)ME zOgw~Mr^C%uOe)Ho)Ha#tWWVQQ*#A+j*oJIu1%*QXa}xCJx|Nd?99eK2veA&*de$-S z9a5++3C8{*_UUHNlnLJE^Pes>v_p!|lY~BNH<8E4L9k>y`|kwM{$_<+mdKyOKuoWIBfqvZ%%EFXRkw)M^9)JY_cCK3g4%9{DQ*1a;Q}Jy z^)(P~Nxhh`s>}I$Z()aaGqZE<+dDC`DD+oCt~N+iAq&X0-qcpc;$~Ufo9nX>&(^+! z5Sul}FZ?|Q@pnlxj0MS@=vJ~m@#daD=ctlk9hjr8?k_DyR z-gi59Vp#|V!O9Q{*CDjb2hOsY2QT8s79?x6o?}i}QxTu`Sk8u;N1b6}Qru?-FmfrO zB!?d-Wq_QC3_hpw8r`c?WD^Fqx@M~tNAYJuHg+1 zfGRdL{2L;K$A~43kk0T=X607~It=-bc1Kv!P16O}v=UQ5@Nr}z13 zqUnwe=gW+@c^UyfQ7CrW%j^%6kJ3~HrHws4YkKa-9`R!w!Lkf}U3|*yj5H-3_a^Ik zWnmJ(hK7_;4g>4<#}-GmhnfcV(<SeqaEZ`ZMc9JI@{B4j%jUd<~{UMP%p+qXuZ6xmE6qjY>Tt_8e?FWK+Q< z9QixpkRPE9aA&0qUsW|M01hiQ#tgl2aHIQlLaQ0)iI$mwAJ6s2=bn~e-<@z%N?&xu zwK2bDRkwlnKRl2JfFy}M>(pW7_gl&|`<(RA7)*Il4E6(gcSMdIj|{yp_1|bkyv~2&d^-Tmv5fWQH+nO01&^vJ8 zBKi`7BE%P(o^wApr@W>*Wm13JeqMH(bJ3Ro=>XR{k16`a^x`YeL2bQaa1W{D}_8dWjM(Ii2OCtlPmzBWo>{)Wo8N zb3h~_E}jJt)X9@I%_SExOykyzJ!51Z+<9-Y>zDQ0CN_#jY7Us9FtBWORo_3xEo0oH zo^GxnY$Rs!y0cy-$T{u$;P|5D{-L#;ABJ%irCQU|wPxa0*ZN~tp09hFNe4_P;#usZ z6Vh(2{i3I7eAMI{@!RIWzAx8|h40h;Y{-ms(UtY42lwjamQ$+0Nk#R>@mEqI*l5D5 zgozl0V@vN?`S@8gQ`4r=osdjl7y5@^#jITE^yl0oI&B`sJXADl$Bh}TXSNZG=i*b( zjmk9(=-*Qf=bF?uIb5HUi(9Z5Z_BE+DZAlx64Iy+Q=`Q20*Mbrw5{aM--SASmOC}t z{=^FdcsI6X;xOIr;^~6iwYn}Fq@EG) zz92{$t^OmVUCkWSphoF2fchD$?C+Br+p2EO0^r=`?(T#>Ea~mI7kGNl`%PEMA+P7c zU(KxhXyl|+95&B<5nVw!?HnA`b-cX(n{@UsZEd#zqUXA~`_IdY5%Z!7kA+of#?Uor zG#-Neyh^apcJf39MJG3J%0)_MJBOWHzPU7LYX08sR8)54I0etMi0_!A{5&Zuk;(n| z_$xVE1U9b(W)L+*R21@tu>6>&sBDU>QASZEa9&BjBK^2s zXbZ^y$6^f#CjOb8)Wt|DH12q;+GMIerCYVY1y;HRhN5cCOnT-?ww8z}GAS9ZXGvrwgauTzzFz zLdX%|Pu(-|aVQw6tYkLU1qZwfdSobWu6BnZzQnI#wkapW(b5^OPIii3^}Wl4E(m#m z{W2>A3&8yOwiELW!3d&_J|l=a{>#_Z=h5focgO%(ax>1rRY!F+IJkl^C%^{6T;VCryxf4}Todc)zQoFdjQd$)TfTo@Z!?2r3>)sPUg z+GPWn|7YoS>siooyY!D?4XN~SJ!GbyN%pyVVJV-^RPRner3;HmRzi%uZ^zwdhVr+; zU1%`3^DutV`OZVCrr?F{z5^0yakil(-^R~eC-7+Zv9SmVF>T82TD-{38K5xva#C{i z0e}RX7rZv@Y2M{z#OfdK&2#WO}F(bZ%| zeW*hL07GX^i~#9{a);?)Oil*Pe!vcP0*|stZ;_>CgU9{LX;TzPKSvd!8hr}|i5D1P zxm8>cVMR**Sc6*;{ee%#b^}{>Db(B1=@D`z+#W}@*v$E^4>d_&BP39#H13{P$7Id0 zBdw|4_I44Z{+Gh2)W(CaYH4o@stCMcIW}%OC}X-`!~aztrD{A%6UPe7LKrPG@*2t< zr#*Jd`ZJIFexbAUQAalx9qgSmG>)ZAL?%VPc_0-m8W^JhYX8T=CMRhAV~H75vH*Zw z#46>Z)7dG!M4*3lVUGlp!lHh4&M0Ox((Cw$I8Py2!fIBphU!bX>{9p9^ z|0?*$4gb&e>PF?i*DHK`vf!`r;ytg6a`v6@|p6oK7do6J$o*A@S-W#JjbZbkDC;+1uBe-8fD z1{dxOL!7fUn*h4*`P}~sVrKxoh*@bUi{?RsE7Q-p-2>+#O}3y3VZ@yFaIpykczS~J z;hRF>BnzHq3Bt!g#~zw*M$}l}*wEB>D>*&Ss;^H+s+cfx^Du96VYn;%zqQR+`mpBr zL@GL|*=k3}Y2Ywrs^S>@;si44i!)V9k+!D$ZvXNQ1v=vYSnZ6QafOaX1XdUBzUgAc z&{5Ssqz+6k%V}Ds7k}SofXnS5!s}{;2z?OqPm?*=9gc;O`rJnAR6>Ki!*0HQe~3K< zdEIMQ^fm~tma~!*Nsdx?`dm$4kU$N{D7n(g&swcG~&+u-5N_4V~U z-o&!Ac?lH)S$cy>GjZPC1G2x@(f8~Ky_M;n?AA(Sz+T zAiPJkqE@zD!FazYw;%MsZx8?m@wWQp?qkY=qI97T$AN%+ct*3i@j6ybNDS6zF&3-D*PIKuiwJrY_k0)aHIsuYW5mcc(0D zhwXVX79)YFR5cborGe8JdUMh3Wjq?Xz~5-{S`(baccqo6YsictczQ1Iau6*>Z`t<2 zD0|~b*^pV&OZQ>^ehLB*P$69AG#!lFW$-<5{R2ZDt@q28k~&6G;(CvM1?$7Sq0aw; z*(#_(9vc`b#{Ro7tSD;v!%XVs7o&3NiihJ?a!@;W;acw#4(*CVt@3sNPWFd|d}pHV zzDZyW3fZ}fd1r1c`{WW#Mdv(xQb#COtg(hJYx%#G6~jwgxiEv*;ZduLo6 zHzf~72U+$773-8r)+?>;l^fmW0L{fW-fkK7&PuzcLe0uG4B_o_DuO>uxtsZC~UnIOzlTi-qU&AtYGOK$jI&1c?hxxsI z=6u4E3G_DG?2mXX2=*BN%j1+HyEo_69Qf-|KG7VDZ)y|9{)4)*dtdjxVJtWC-;!GH z1~-b}u>nH*Y6+;4TXY9*jJc;6xu|zkA}HFpeT8<`KxHc^R^9<92?Bjq3IvK8Dk6-VdF9ng_owu~x1dJ)g++KM{vhI+ zHq?(vK(^frumNL@RA`T1szn#LPB+2;+spMx$;%X%8`8-(hh zRYjqxk4vZ=pW0>R>6933J4dsUrL%LyEBb0Y=d4Z_hJ06YXY0y7mDBcosi==|6|329 zBx*fxNX-mWySS<8jT9);+ymdmnn#wm*=CoL1{`cz`LQn~eCE{lL;)yaDrMk*SsX0bZ zz_%=jKz?90>J26D%)I+L>kg|W~~a4H}ia3EnxE;8;;uzx73d4h0Vol=|oHv@Nm$mHw&$p zy@5efJno(I+yfaP^824e9m7=6g9V!sL5so9v#~J}I>5~NcPB3Dx$@fM?R@Wjg+HH{ zuq*^CtL2}*8dJLP9=Fo|(P-^#Ar*1$hDe?;RZW?B4xk#w+sBO#8Ru>RN<^)c@;K9Myp~pjI4=8 z6*i~GocptEY`}(Hp`g-;=H6PyxbC-p4%eZ87|r2Csq^UQrG5reQ6p)e-{j+OBNafK={pizsygvsRg1>aw~PhEmFC zCMo3=Sq1uBS3Ie7UDYophTHRE7=V;^*?kQRboAdAmhMQ7R)#7n087<5fIxTwIiy=s z{Tvj1booFX$odYjQhlhYoW74)g+a*3-mLRGp;)heYxySUV7bDbi6A^E;w`_lYy44w-|DqtamIx5;>(XlWI|1+#{(T;Wy8S93i z8Oh5*Nf}iOr)ICFRbOj$fs(spVsyuWH#Ky@s;acIah0$@|M~Yg=ZJA+=|1%;oQR(?flG4hLOyLIJv7blHtHQTVwZ(1{Roau`7EL%pJNrx}U zIg0k7!fjVhx^Ys#jjhRazt zw&<-jtY$WlxtjJd6YVpeu*xQ7sQ~~EjXHlima;Pvx-GMbaaOFV&w0Iz7I^z7S3K;d zQVudR=bNCGZA!`-uEz(CW4gojxP7RfqxYwZmR|4jM;9IL$9Rh|#}2C|r;LJBm-PI) zcl0V;XZ7n3qfivZV!mit3tBwSborhMXW<=}<~6>5}`vTH=Y}2+e&&^OENKm*V8n4l3V)4oJG` zD3@D&(L=z&>ro^h4L}epEiw^w)PjM z*(`pZ6X>qc7r{Mu{Qz~tIm>yzq`JF^*;p%@X>@Gp8aXiWU8dF* zCKC)Gu^iFV#2L0KV>=0}twp*U;teW+qZlvnBf{^M->Y?+nWI{UZddmf4EPNZ~H?iH?= zl1k4eZWPEw0N@x|T=!bSB*3z4{CPr^t=l&1f5`C)WP&1v1iXp)c)fRT6=e|*)7qaz zcGY~PFqRz*D*n;#SR(XVfc-Vm(hg(cx!+4iPZ;@5-sTPDG6+JTJm?dNc^V#y|D((}Fp$8}41kKlr~k?^zbaP}HP{fiu!C#Ah^%vw+VA zGqjuX%4bjO?9uNH>iedH(u3ec2sSIl9bZ&qe(!PCk%AYiDtmWbYA80OL6Zb(wt0@3 zNkNGE6p!WGR?&BP4Kc6UT ze&0T|ymk%M${P_n7`Gje}goz?2WXY1vV0ZMKQ!(VGMxVW}?vDn%G9Q*{Y-G^tqlbv$7v0Te%pJ;c=Ah{=9>3mi2EnhABEKYSfpLj!*Yx zJ=>R0ot>RLO=8aXYs=K5ftP0Va#)Reuh$lq*oc6MgE&u?9~&vT#4F{NW%f$#xuTS5 z-<&PY7tThxC z48mO7i0(#5yy>LB6K`I<2QR{WI6#sAdE@x8ET`n(Z06rR)Y@`U_V!&8-$g9_Y}mbK z^_2POQ}4FJk4HD|l@q+^%s56ksL6v>b^KOoJ+IyUJL$vVYhSm?*c2o9D`{U~mjA1q zn#iU-KDph^D!;F19*X%g*`6Rev4%AAOYBI2-_u541oei@;NPzKHbE+Y=zHNbBcNEY zc9SJXDJvll73aojk**-T1Wn99er(gz{Se!;sh^KFzWKQF;AR#qyQ>Bs)UjJqAROY* z&wQODkM&{`AbM0b&0Xsj2$5TSFey+qkqu-YM2?f8kghq&z77N5Qfet9$WKk}C1qQ6 zN&6&*hC+2ELx~0#_6eJVYt_5>ahgr`ZK9vyqrT%8l|Bc*Pihxmm(8VJtlqATcWJoB zolvpO(c>t}pt-H**#z;&jr`_wXx0h#4@X!=^U9Z(9A9?YdBRYfx~UKTi_T630RRBj zU$2k9r;8Jan`IX~T}#1J>n3FnlgRo-kZn*tkt^$bD{nxV5Ts2utLT3{gO&0(JgF~i zEe%Wk6DDw95FlAmQL)WwWsR$!IQG z0O~ME2>b<63g<_MsPrYwCe30$csH4M-1orF;ID7WvhZW ze0TQQ4V!_@*f%-Oa*?Og@#ywR3?IPw8f0GbRqx9IOO~0fw(RzdQBtg}msrq)cAw7B z%ACIL%l+8NB3n7q1``jTpa-0^GNdb|n|?Xt48WFB#L?btGMuW?*2*Fzv(#a+iYCL~ zB>1=4_sic3VzMlR1cZG+D(m?dg;KHKp)xK8wjYkavhh{NeLSATZsr;Vd2MZIIPWb- z6iNt=UrM?={jGXu?xl%){DCw4nMt93Zy6hfTs5yxZ?v&BE@{V;{%&%dBqki`HNN+u z)h>Db^)($2RA1nJ)^md4``w=Z5ErMaMc`0aQYg@BVx@>u ztcr+%Q^UYso;4emkcecV(JHMfTX_Dp6dZSP)!)0~Xh_t#&be+XAWvP=etWcxTT``G zdz4=NKmz6fydYe47M;P*vdrj{nLAiXk6H6{WJaQttFGdG=*3F$2Dx($L>tQNEI0JjyCWv=f911t?Wz(#hmy(NK8Ugr;sR_fv!gSFH5;!dG`@Z65_(Ii%?vGeDZ-!Q>GK}*n!F$hnmzy0-I zFC|oS#03+&GZ;{0^?y#ceyUq=nL6)dJ$tWskoVBA2ragLj)*`>_v?!fI9~A*0&LC5 zT!{NWbh~71%|2|oY&(aH1PLUBHkIK?E^Ih~E?6%L8?5@Q!zw>RyY(sYzfdgM!Nf1Eh7x7&}%f_{w z+_~t-e;5&ZBXRfyn81>Dw=H{pAHO3j@2&hpCnLItDdIKntw&#N8&FS#b^jXuWoqUA zY~Shate@9^wc$eM>u=+|)-*05SlKX;N+>&={{f1+DQ$wHZjo}#)(5kb<*P}qasgOj za+9%9Cq)KdWN*1qB=I*l+Fy zva0N(vZ!Nh)oJ|XG)6<6UmAoRSY$w&9TiBKD%wmOq881lkU!%-_${m5?Mb>+t#*{@ za&i9|r1uPk5hdnVAMCB6@x+~Dzj9J!n%%sR(rxPS&2ma_w|XVN3E6=yZe zobqSz=vXN^NPQh0me$cZz7Ugw0`kC+7_S^Lj{HkapmU(8A6+f(3jT$U>#bCSar<8r zO}^N?yI3g0deTSF*{nTs+ZYnO@741rRmM!z)Kpu0)dd?S&-@eW3=+{m&`r&Cr?n{9JVwzw#qZ%%>O`~ znhJ|vuwbQxmq$ziH-wpxl9rc_LW0|3Arxz>zY?5z8gqTl%V{f}j{3?Foai$@ZI}^X z1CwVd^hdB^mU%WlUwEAua&X{9^pJu7)3}eJwZupc#S_9*JcGaOu+d3~ZVsk97Z?DR z(rX`|;fuAL&-MNpaE+WO2p%t(r@CkMc2~Tp8`bpU{f(l)@yOko+__sdjv83qsp`^M zG&XsQ3?IAG;BX4z)r?}d7h!Kdmgu6s(eBn+v^sQ)fE0Tm`x+#`(dXCzb?kkI=Z<#2 zSfQ_qIaBW+sd6pCn&GrXF;xyl+YFx7Yvxa+-_KRbeSUP^!BKOrkemy*sCP<)*_v z9V`H~?i%LfLJAa%BzQjShT%{G=LM$z)bzxSzrV@x_mo_|5iISzcqaj!#Atu>t>Ojy zeGA5WtnF%S$!y}MG=zstXx;T*HN*o9rNqmiX=P-$?s_iM9VBAt^>JNf9fSFP(Sna( zP|ItZXy)<2=Gs~Ixg2QIg#}5FV9$%ae&M*hp+e#SFf1wzg!(x=E=j;}zUBjJjSeWG zaK_{@o1!bME^KJ|93HDx`7*A)@v}L`O;aVJDmO-$IE#`fp5Gb_dfV zVdPo0sj{VQ3k4aAbu!OHhv^6X&7X?DI>`}q?3^e++!4LeP(XryI3aiS2T(R25YT5} z&DIEbl#a;UqLw6V_L-?7@O`ForsrwuAmJ zk3{HU4(HE4o_E-k6?Ci|QFzz@fOK-5pJwjPX$dxm=cZOSY1MtrQn4>DiajLe|4;UP z(Ds@&owH=M>M|`fIGX^g{O6iVGQ6`R2CbSj#trifb+yjA-(JSe#%`wgiSf}{MyFrDUY!zuHpc zwnF8uo59eV-|BmSLjrp3j#O{~y=D&6i|1}h$BN>-wI;wR21EzJ4%CBq}J8Kk77J|QBa!iUIUNtevYk|2Xps>;e) zsed0D%F4Dau|#d=lafTnJKy$whb2=n_T4X9@Wh`L6~RD?=Tpl@=bRC)F%~+zh@LUk zb|NbLtL(u{&+*euys+?q+u4e$ni>TH!r3g4h{r=<&P7&MRw7J=K4JLRFQgA=7b}eQ z?~<0%*uS6|zZ>0%e!s>IM?e!jhE`1(^M(ZbXk7-Sw_tS zZk-a2iGewJaz4F%#Bf{aY=zO@yVrONRR<=MzVjF!XyWLM^*Mb0js}9?%22AZr)umM zd<4>=tNRhBVi`5*N=Ff5R~FfL+%Hk*Us$FILe}TusGVU?2oNg~7ZqOFBRI2BRVV1T z>i3zIOtS*`m{MXwYF?F9*Lsnjh4@j#u@#FJU3IyiQ^s`Dn?Lh@wY}OA)z#INDyna4 zTHrmgb?cXTg7?(W&@eFAYO)qxijaW3$}W{_K6o&P3ZQ90UFz;<0e;RV z3OYJ>|J2V#b)uD~4r+2=K`Fa<{tPEAm#nI9MKbU@<@q(pH)`hQb`pC`1ZKYK)&;W? z5VrLE>Nstr+}66J!${{Z#p3Y!Q zw;K!6?8u9KfG;3O^YZS#>2a9luD8<7%gycmQY=gSeym=c!5;Gc`=^k=3Kj5xFW(k$ zv5J)|JQ1*b?(UX8?zTg*8T5QW2hrU;A(~k<+C@)q@KClJNn7JPSwSH-hbh1+Jx)-I8aA*r|(g<%BXE_l=S!bOU zva%JXA{*pc3-Q?CGRn!!X319~u7MhdCRBocR9=+{`s8^R8C`^F4hwP)evI!xAUp{#+Kgv-5BXeb?Oorqc<##RBC<(Dz zMbm!c(UHI?pqm~_94%D2sgr)d8)rqS7+S;66vLxC`72K$6y$?TRHQ=#qzDK}Ybz47 zs_R1WLD`nCGFO##ysF5+HBLjBadm8l?x%~7?6^1uRnd~?5i zgmi%lCl+NfgI!k{85zIwT~Km)JU4JttKBp5aF$&SvK(|FEnb7UiI#YUtRC}uk_@g#K*-6%-zUysOk!bbPHgnZ)f@t|t)M+?s?A3q$q%K`DNpqr#7sTh&!;=db}Y zwsK577G4v*x93j6uh{qJO@{}C_UVT(Kbq4kyr=Xm@Akv*L0lPrr)*|?B@$czF~wi8 zRK@>kjPm30e2OQr9GO%QXy3_KvwagQ*CO}84EyHcC&2`w@NE0S5xmMW0MM0(T3MUZ zX*zY?dI`QS`o180(Ch<+CC!UQ!^Ny?r!%*LZwLEy-Q{Io`nf=R0S0*5?o*DuH)+>8 z+-J&FAyAQ*I%O_l(@n0{o()VcfF`RkoCOP z(sO4fiQ!H9*PbMY*G?#LyG(w zgl-ToheF}k6cQ@*_+{<0VDT-9eKYvu-9DGfpw7T%0cm@CV3pmL%j0ru@ikC7rN$#S z$|7B+Rp}oJHamw_APoK}6RKEc|9zLALX~w7 zf?^s;to0}7Ce81^#EXV&W7gChv;#`~N|VD1iB}tz-D$9;#Z;=D)?Sx;W7gLA87fr# zhT>h2By+2E8T_uxOV%DOd$OP92OJIm2WRIU7Az#XWPS6Z+l8KkL+y-3;z&OR{JzO( zXg2Hk?@Ct?bwbFwXq&%35xrQ-cGl&csv`4EtMh5%D4*x zFwJrJ#BWbh+=u{?_EYZ7tj*V_3;AzGaYB62xM@i#q?Y_2Pbn#hyu`tyFD+mI96)cF z#*_QU@rD`jKbhE_-HeOV()X7j^uo6t=0(xXYP)Hkl_$qXy5a>!!7tQskl?k;0u>=~ zaaR;HpFSmyn81gSaZteqjVc=m6#Y?jIQ@4Pz$Ew>;s3HP#OjrXz2(&whQf=SkLOD7 zk5pSIx`%hB-h?3$IBo_)t}+)!h^=lC+@p3Kg~wTg!A?Z=uLxsvOv?zif|`r%ZwfA zkJ*SR(4y4R1GG;JM@%6)eb5D$q7(y5$Z$IteV+z{Ijyp-&9h&yNZ-n*XK#MchQ6hF zr*BVL7@aXDJ;K}MMM1hS+V=y;p>gRO-dsWdXO*T|m6u${yY^t**A7!GOOekNRa1N$ zpeW8mW-O^1B{BQI`Cb%6LeEXZ9iX~d+W+aXd$|6R4tYu$SB46Mi{KtCa^vQSha#b9 z4mgx8_n)zu?b(E*pLI(r7Fk->l&E=qpDYM&0pDV_f4W?-u{E|G_+>f2okI4&d}W#m zoE%+od|f1A<+aEXv!?t>D(r_cG`y_#+7f%g^d0uFdMZWT-?4sFC}^P6QEM=Jw7;SF zbE$?2v#2F-wn+LSMSbt!06wI@d)2w^;dB=0(mL>zU_|f@Q+&6}YA&Zs^s=IF=e=x@c) zc6A6~VP)U?45?fOb|#f=3!;(c2y-H4A(}oUcD5EmwgvCbQ&&`DXLm1K<$+Nd+B~P9 zKGl)>$%GE(+O|L6ZQ~P0m6}4G9E66waVCze>6*&#skQj_&xt!0;9+ zxs3(t6(Z@lX&|*teJnuGQ01M7dDWJaaEzSPV;(8AS=Es6$<-&2?mV)(ax(!u5YuZ* z#R}9$EE?jbg+oT`9llw-6(0qC_!t>c3vBd$e2z%QC)=5pOB*`eeflfXrkt#uv8YGS zq&8SD@d|lzr6Ty~6MCYnp491*(!!&xnk3Qdk^=B^v4{Sgq_bgc@)X(|J5*Z#ea)Ba zyG?)EB53kySfs^;(>RY&Vo3JdR7ozpqNc7OK{B%17pbbntqT97CQznke=Aa?aDwwb z%G|!hX01rCl|fA7doVK$|0gr=zHIqh*_0#1{Yn{(TQ9T{=@hIr6`y;f*&FU9Elldt z`g`FM1)==^3%D#i)#gXK^bktWBk zC7eK_mn<^bPqeygO)^ro6nZaj5j1QEi5EuDh&j3W$^p~zbSCI_bvi2#eTgV=3Rw12 zfbpank~Y_*p{pHxx7a2|Yl-{lFnEeRWH($w&gDgio!NH6LfEXy1s!f}&RZ5l2Bo#f ziSaKwA{n`385ywgH!e*mz66IuLWEj2Sm!OwZlm%0Z&mUaBqxrW+u|2@8&eq^^I6X) z@9Al7I5Pt`LVb1iJ3lKs)*I~&6&3B>+}srO^%WGrub7b|eHJ{GL~IPn-$S!3RlNgK z^#4{j{-KerP9;OyKmQuL@|~&peElvbj&}?;6*Rywq18aGI_h4_LIo}%%IwW|cX#+ml%2Hdi`pOe*IYM{QBX2j zP3ylGTUlFMo10gqcF|#3dH9I@zmR)G|B1b06#{vm8i`Vsh!uRc_Cj@n5&mrY6H0|#025k065=!NN&sLyL z9R5#qdIqC)u}c5EBTgcJUW!KrJCSwV>biAOJ(3gjSXmj4Ihy*VW19G1a#d%4<+ysQ z`9PdvAxxnLy(iGCbU<{=GK2SZ7uo>2aQmomDIFzw}?WfM0bB%0Qjpyzx<}~gJnfN zebMt~e}fgz{bZhfwT*V#>{|$`5;ynd<&OO-=47K@7Gh=ZGm7E-YVoxkOC`2ux?NY2f{LHAgm9J>W3UKKaz8 zyCMOr(TD%Xy;VS$oxwyVb*|#e{fdIF!2AOhmF0$SN9LGri?x=%zP_>X9&Kft>Vxl6 z%S~g!xR?D)mSfyk42i*Rf_raq7*U~bFs>{9hzw^=%SSwI8SUp8`7X;To(H@gF6X1x zg>4xrylVx|1#_xZ&Kqc0*bqz5!V-(ZIMs16-rNTdaKAr5KnP6=T&QWu$2(>oNXlGu zPcR&%Z-z-e6u(IPJ_|&LPVrw_mjgu+xR+&Tw`KXtWl!ahebyY$(gTGE+Hu-u3V(y} zE1*2@kJRveiqC7-M8gXFu}I6<*1P9$o~Qi5pL9?=#pedM$}INjW`b;H#`nAS%LA_N zCMAVz2iC!ORc>wvEu-&SBqx|QU0Gg!y9K@<9bRhdJ=!lwqd^Pp*B!xP(_mNVM&?cV zfKMGiUn}eG=4C{1-5Y*D{n*Jw$IKi{K7?J0aLy+SErhmf(~WGvEW=M;6h$I0Bd4pS za0$MezN~jo;?*B1skaNx4vmbgtb~MHpkNz^($Hsc3ti!eTb#_#Mi?Km_~7x0agrVe zucoD+PmX0MXE-lfdon_Kh9y=LX;Hmjs>E`eG`*e0hwf8kKML`6CZlVVs!=-jUnKP6 zZ6V&&7`{##sY5pr9s9l>TmZ{-T5QgieSNOsHw=CNleR-p1Jq9+Z$WN3jmh^1f*pQ4 z&Ic2YS>7%j!@CcsHB=psCPT}0HVvNdKW$CP5O)UnY*aqiw1Tz; z1s};7XiHV7Uy`4SXV@!UI7BG@r^r+VtI48G(R5q9Y8y5r@9t2;jiyb^*BH{zdpeC1 z^)lkQ>}>UPLH?_A(fv;suZMZP9EP7F5@SAYVfn5Y!(BE}@cJ0-Upq&ORd;}HTW7h) z`|x;Kx+f+*@}$T`q=BCVVP}0eV(-QeR4#lkw+3qE9h2x+bgOqFIoJQF(>XM0Q^rf? ze+LW>4Z$NKVt#-I_aG<7V6H^Phi7eeKUYS@kZi+XXk-Mlu5NFauC#H}z0S$#En95Y zR&!h_p05GY3-`dB9|LG-2nfT|( zIOq8KX6`#8{46NMr;4>`KLC}nL;}zWsm6CTDtv@{k$y~kg($08`@MJ+0zRBcAB>+PPV%lz>OxWh!E*x!f(>U{CGm zj?ZXyarE&50Q>TO_Hb2qCfhp62??oc|6%O=p{)JtW7{;#FV}@;q^hv@Q2-S#gD_oA ztuH7X8<#;77Rf$T65>q+u<0RfsqqL?Cw9|oD7e<*dQq`$&@nwNNkKw{%-9A^$SExG z;r2m96z1hBdUBR6MS3Yeza=ajm6)%w&1ir;V0izGlUAN1ys53OsZhz`d4$OXltVF< z6pYx-EPIT{Ywj#mRO&(YJe7-DAqG{#DGUYn-T-QGhYN1%UT+D%MK2FB=m9HxV;2vBAcb^!0|3Cc&mS zZy*r^f2g^L1yX->(k`>+VHYty#)&JyW{+_y_5!{(|kUDCvyl%^4GyJ4GIxZ z)1If8J=&L&pOmFr-1%)&F@GdHEvChzv9osag{Yw$xKh{qxt7x=@S+_G*6`353Q~WCL%jiog>``F z0~4eV=<puPpQL@YBO)H#vc;OcpvL(CThAXtp9$q#$dMb}w zsO*{;ziY$~urGJ7^RIv?b;}R{iL`>!HW49!1kckTm2b0?hLuY3;5x;*OJ#KQRdF$A zyt2?YrrXT;$T#l+YGszVl~_I3C*$Vbo0Qs;SL7 zpZoRDmJ^>Ibm4v3$&y^0%{bEu+hWC} zl9;JDLwEi1;qaWr!N4a8hvXZ2>9#`>bPg-uDto`x85Rlloae){Qyl)pTd0! zaQ4EZO@NJU_PZ9ZMZUy@rdHPxbO@N;Mf>*I4fI332}7OEZ5JK1Lt^XOq<(5L401!*})Gv9%KpihdSC0_&e! z=xoN$h32zIvw?8BFK18y0QnEahOGBuQ?W1xM!l5p7>uBTWpvxULbNCzXq==iLbuh9i}c2KWK0AyP$H){K33tWgEBZ^$o z!=lh2mu>I_IdZd4T$d3bhy+8J6`|!hRFcA=$@h3*&`X%#wbtVWHdPCB^N0{6Op5mB z@kLT+at?;qRagmg&wF89Hw`wXKt=KLAKkdY*0(#b}1{7 zOGA$Z%WYalfSLh>bc3yi%zPFDe?C)uYgrC`yteo+@(%`P*8H7~@L#%c6d&o=F3&4& zvNCXZ&HeYZ<_EZ*n&Cny4FSDhXJWl^_c(o~B}0a!{4WlXOrA#5VD1}VHX1`o2g`oz zs>rzf%Iy%;ql^c$Vqw|>OS|&vjqV@OnVo)LPX5_PX8bqGd&aIQ<#l{*548f_MKQruNv_;jw6fnOy>ik$z&ReAC z>yYE=)JRmgvtW2}h!)X3LAM@vR`GbOu$KIbMW}t}-aJjzRPIC8VzqhGy5mzxn0NHNnggs z4zHo(SVn0jKQ%pzfzhW^aXqgel2w%fRY8yY3%*Tu&&xJ&?{4DQ7B{a_koP5_ zrm$Y4|b_ z@m_qI<;D~xYs05W_x&);T5HoFcXYSF(d;RSBk|zd$B4j?OgB;)r^6nl43vTc!&zXo z43x9@6>LBs48u^UobfZ=+R&dRyKU&aLMTA%3yTCL^Jh!wj{^|;9N>Y9ft^YWcSeBx zI$fOeB$&~_RbSR}H_wQD)U*RU&vtb>)Wvv^UT^h4Zg|}U?tSM40OW3R6zz}z62KW| zoAxO9r&`JI%iENBDw5^aSex*J@MsbnX4HI{zYZ_jC{zHyk+KD|J7{=vNrT(uGUbit z;=& zZO?Qtw76xoLl2NGigVxRkGTO{CR?LI_ven!_nK;zOws z^2{^FR(SQBlxs*mrF)m3|ctB=6oCQ@!rs@fYKLw9D;z# z!6ZANiy9mM!hd(J7pz;fLfx)TL;QNI+(0fsXo`aEcIY+K*OR;PBRW+5TmHM-3CF+( z7Ux+AhOF=a3k+whNHIc9v=VR$>8DTevg;#=C?v#T)V3YS9WQ$c2;zak!$#Gco0~JH zBLz-dMBR3%XJ461dlj5y_d5#Bl&kGmk7^u^M9#sY-}}@fkQ0sd;7;@4JVsUIhu* z)O%000DBGxh>-zDz>A?~Zpv}pj5vHmt(6oSv&ViIPNtycrLGNTU%Sx0l)ZDr^tB3cmFN`;G=gho14g< zYatB#ajfKOv_Z!sEQ!DM4JmPhzn>o+orC|9Ea*2Knmf~c98bS2RZgMb;``*DL$3OE z?T2K&E!FRxj$d@Nv2~>nq)c#Ae+ja+!dxHYL*MoJTF#|gnzDMPXKTwCJpi`pjTn{r z6%{-l>JkSBxV-wQ_YQU#Eeud9{M`uJR~m@=x#N;MzP-~nJ?+Gk5gi?EKp+BD0Gs

zanQ1yB#2pKRX$)@%_n5e1IrSV@~C<0RP=ivbu^xCzR zm2cO!dM2>2w`@DS(`s-Tp!L939>l099~<|I{Q!+RdjZrmPxqHwIXO8az)G2Fx0#G- zGbLAPS$J-W7JRNBb0gmhA2KV6TSX|4WwBM@({ReQ$UQDjxrjQ5J;moZm0X7z5>>zP zw>cQvJe%%$_vT#_HOQx7G&ospO@<3G=kuH!PD}~Mc{w|4kbVb= zkC&)4NP4dEaJf(4D8+xEdBtTExSR&d*l(xzN{$h6r~Kn~K5gwoyEt6xU!um5!@WE1 zZ2XDQudDDq4+|q5Tt(cp|F(^S1O-6q34RaE4)(G7M#aog{?iXs?=0CK-+pnx`dX?fJC}^nXrDAz@eXJPDxBmw70hh4^@4EKj{bmQTp@@3__NIaCbhJ zmbUC{`-x2ciL<4d_otH@wX!#`aQ88jzq0jO%2~D?(faQx4$;32xCHVE#yv@1PP!i* z@q#6Fy_idX1IEX?$$!M^P`>v|hr zjy>ZUamN-ePPY-jq1!3(lbM+fuaok>pGHtc{cRWV%jywj50+`d*-D2XClzqw09Wun z*{7hPm{VERX1Pg;6wS!kmAMpN=Irzn z6GkP>VNpK1?bj_ZsHiNRxy)X&%fCFSbu`<*>?^A-385MBFu7bGcdP3>P8Hj5@d_$D zGNT)rPOuHXf#T)s>*^>dDGl)MC$1gFhZ9}Zu|$mbSS#D=-$C|O>M!6U)_+(G5)lwe z`@ELwt-0CF=OXTp!6P0ldGNrw(h9{q_ZSH*CP$MpgE64{G2!?);;d=-G|EexIOkm- zHF?}PuJ3cT1i6%igaRH?NUuSMX;x5;X`xb>^N9?J_wD9*2C$^0*Wk+c<%#q4HTWnm zU^FTgVxNOa>{vt|H~&e2nG`;xuP9NvLa(HCo}Z~NikHvrao$=;g~X|(kck^tXdiWS zM9_Tc+SpvN>mLSfxTQjmp=GjfVx^wH==hcQN#F4H_H4oy-s0tUZHAxBr}@hd%glH4 z=Gzrll+b;L1<2!&3*xL%i#XyVr>BLt7u-1V__#O&AOrv77$=yuv(xoLCm^Is>&+2; zt?Xd<{Oj)M2=ZsAO@rXut`c?5>WE!4Dilz~$60Q>v(4Lm3(-MMkl>`_AvYdlrOj<2 zh;-)X6d8g~HS0grm{aY-=Im15Z`cGfzTgwVh z`Xdmf)Gczd^3N8vF9JkV)|S-Ta&sN~O8ij;pBkH=j^pYq7s_>>C*%dTwCGN!zrS4m zKnv&B!y%E!!p08|cb3_ocsS)&Q@!8UvZWs%PL20T7+Z1O;HuXcJu4sM&u}%~iS_JS zG^rSx)szTVrAg!l{B9A+%i()i%n6z@r6>odZcM>^C?X=~K4s98dlQ|e;?kQ4a)6;Y z^_FGkb0s@q(}MqY{>=ne0UVU#?iY$fKW>}k7hTUDX$BFA()fD( z%IozA;{cfQ-`hK*VOaNhY0NDS9Xr@@;MQS)1EhWZ8W>E#PN1o|+zs$i&mx&0JQ()gd%F3 zDWU1UETeHMT=f*jL5zlobFMEAOqaJ_Tbl>OLOa;q4F7wkhj%#>EydPsDtwxIKPi5v ztn5%#SHSLYA1>{S1eoy|MkNL=+rehD$9Fb+<3v>is~U%{_G&CMZ`M~Y%ZLo>G$shl12W}0kj<`4?*J>pr0}C+&i~hxd!WR9#XjHN6@5SVO zX?@Y^>M_`daq|*qfKB@o8T}`wuKB5%xl$)cYH`XPwj*VW*GUxCh~9Kjx}hsZT1|R# zzw1W5)f@LOTU0B3j&BsgFt@%!;DqGQUs-q6y* zt!55ZR{D7@@>(c2(ZYUKl{w_$rgt6%SF0g`+XpghR^_+{LC~e)av}Ffk~QPf)k4SH zoE67^WZV*^AV{gGsOacqNYOwbkaoM*-gII~!`ppyicV0UK@~CApN>4E0iu5FT{#6z zVz;XXaE($-py-;dBF`n@z+H zPdgC$$8!}muu5Qcu}~GA zAb;kP%bHZQPR!<-(mLPKS-mOx9pk&Fk%^KMj}DkSRn>0%0Du&d@OwuCG_Fr;s`?0( zPmLbGQM-Pra65(X{D-P-oZ|Dip1B~9CEq8^P*RA&#yyp`$HP|Z*wA9gcy0;R*gG!< z9m|BQo&8BgdNnyvP%&&RXI(2P-DstXlGCeZYdIV;mY%qHuKvE2t$_zc3JV4-Ez}+8 zP7)zQJgDv8J~$vw7tQci{rU|+aN5|SuhDYfJMm>#gfKMaQ^25kI{uax2@TW`2LOs~ z8|PCxn8_07rU~NF;kHRCqStLaAEJ62g3HYW9bXvtcumnJ+;gWaTg)}H$nyoC$Dx8TA# zGJD70zxl$&dZw11c%)IV({q+KH(1;j&K=o7uB|-RJ1YF?;_u6#l+A(%e5AxR=PY`9 z5>p+nr>`~N{Y^|euYB+8%1dKk>e_D>wPcz8{jsz%nN&_zcu*rhM4;^2 z(mMMppH%dS2FCA_m&?mU$@68e4Tos(Y|tO6_Yub18%i!6druxNtxpOvhDS7umY1DV zOLZmW@lpNTSev!VT~qX~)smF*`~!gwn+vIk-UhiyPe+VDtx&YfXy&E9M6Fk$znpb}eQ(2p^jr{a+A@B_%oa)syE+cJTR@vPL^N<(RC$)Af+fZEdG_=RQ{Vlt;gK` zx77;VETB6AO>jY2N$*(739BfPX|R~%5rgm325?}1M^(G@NV}O?`@2$UCSwQl;<|`! zI%t4-=oig0G_T;e@60c1vV&q7tJ_1y(Qw?1b|Z5a6C@q(MNB`KeZf1o=G}1D3FJ9t z>K{Sx_h$Z=H>h$G^Aa6PVBOiaoj(qQNQ~#K)y0cM9Xs2|zI0$3gffkFr&;nnUt%%- z3MSKj2Dy{B#lrTW1PVjT0NDK`&F8*fiLFooKWya@L zbjx70+eD?0G(G)qSdlsJ79d5~#}C(J;@;;QoRJo9PgN=F31Soc5X3a${}#&c_YDd{ z3&r4vcwfI0zaM)&48nAt9|c=X@H@n$7`M`-%~ zm7!q?+57wLu4}gJ*c@qX&eF{BL&@uV|D+3Cv>lGm8#kEjgW=uk64DT64FD-8X#f)Fx&aa_1R`X80KGq7(TAz2msw!2fm%*W& z!uf$$sF{k&znz;Ai3~+JJyA`$$W`nMu+RTHug|Md{@kThu9OD=S}W}}GB6(CaT&tq zSk0l2^v#T?=rJ-Qp*uf9RK+cl<}&t#Vy=64M~F3XhH^Ke>%gP)2-@Nv-Pfn8PuKU_ zkij<6lQz?GOvc9XXm}76B8z$u+PW0PQ^D}|glH4kc z2_wdbj^zD5MH?0Ivb-!<8RzfS%h7WFv77zuj7SBp_4&1n>2J(&yFo_-nQ`>0l<_JF_y+@=?t!G9v`<5vw@p2GC)aA2K(svjr6JSp%Vkp0rHVMeB%6=*$H@|EJpzO1D7PuAHjVj6;%G5CSjmfk%yO`bLhyK9ZI%_8p zh)tO6rjm~1LGiXLSm7R$wv#h(aRJp8j2O(6;UEjybbCREhN5?m-~mG29lN{T>oeu* z44Yq}!1<^pbBrINmhbdm6oe78mSb1#FJEWY3s3xSZabgcSL)$Z+BS;Y&#KAr_>Hzw{V61~ zy(s6*na%QKvBepkB0M?2aYgWy&vSvCe+TYkTSrW%^S?MpFZ)f|ZSM{(RpxFoa84F+zF*tvVImrGN<-Gk0gxIZ zd`09mz3B45GN&l~Lo&h9yrlt|;U$^roW?qgfar0{OveZ9MXh=E7uXn%j7W0&xeXs( z{=$6W81z4WqS6NN@nn3wytP}(7YXO$OyJ$NjFRe$ML6B?`(->fw<4>8gTYzAQfs_; zTpH3d4u=;{q3=50`k)iPM^Q1*UBin4MI17=!K%Q_)MH%#$%ZfG9f1eVfr*tDbsu%o z#8ulIxDyd#J6$6Au4Ghk%uIgbun%AtVK}A`V8Dv$56!Q{#51O9Ph1kbjxP;KPS+j@ zwW3wllINEKtox+E)6~QQzV78$XtuEM#~tiV zj;Pr1aMXPbbBAN3TTM>GIzHH|HWM{|MrFs7=mcqKx$cSE<106auBhm_)M^`i^m=B> z`juabSc3s=MR3G)GIwoRC|!yL{cm7X#%(&6gKdr|{m`yU#s-ztQX3ENmVepY@#94t zM*IH8+aFk)a)=lK`w>pjxlkTw9Iy}sH7x9wgDsuMzS#5ey@n^w@nQNrw`R>rD<8@c3#J7=8TGl`93r<^1G&*K7^5{16>Vv|eTDY9m?0naRkX zO}Vs%@9tj=gH5L|IThUaGmJza5lLTwuX&0sxkggcwV>9b6SnLS%)$yZsy?m@Wk>!E zF~_s$azh!?SE*Qkr0>?HeHU56qc%e?bxq2{qmlP@9hHb4)9T31o4kBuuF5*-jrBz9 zml>4`nh#jm`|h|hygkb1Vp-RC2NgmPK_0`g)5s3U(JQ6G=`ZMWFv@vjHsxk7 zKPYX6W9k5<`#Y~7vRx2iMw`zcSJJMj<$ngGoAASzN$&G*!!To9-{eHFlW^c4ycmZ1 z58t9Eb?%q`93vxUzNgeaBXT4jO3>C7uwZAN-kVuTU0*=}8#qfiIwZL8!UDd!S@K}8 z6E#d&3glAq-A>b@A`@e#N|Sio=?Xl>%PrUOZ!^F^tm%l!jZRE&2T)_k3cK(~tF}Lc z9Q$AZ^5%cTRBb5@;hO$8^k%!Ce{4p`r@RcQl&Ox_KM=&y;JR8(@pE~Egrqso0ZO0X zoJ9f#5D}YP^NWJ}%~-TF2wH*oA>31dk>bTDWW;cLkpIJtRt&av)0=T&fK&Jhfo!x9 z{fCnB!<)se1GW?8KH@~&eVG_B8o?vOX@YW0pA;vJ! zC?4uy>rqx|y{{S$&KcgfWd`)K5RO>Pe}%#l#K7tZ4(YQ14q%>?B7~1_ZR=L2n}^4H zolymernS|SW=NGaVR?m{;N2}9;<{hF%^6Bp5UD(h0e&|DB4%QWAuh8EXa_Jb9`26B zUR$oHD1!b0@q4mt9x@-B9yWjCBMP(Ki6__q>_(0W=avvzx@qJeR?l%$zz^!Fqx!! z;1~>5w~=Yo>Gg(VBwbAj0F&7CvkC8`tka*M^F7E0pd{|>?u3HEj_^K$Oz(x213BdoFG2Gws)M_+4pjav=N1-F}lR-_C_{5W+LuOs+TO%}}@LerfZ$vp7G`DqsFn z_L|EwM-N2xnJuhho9}&uBu+>jJ;5c;ST+mL5P7|;^nn4`w%^Tb19t}k^NL0BEvB?_ z4KqL04ht;A9hwQ`tFfOq1hARca%eiq1iGg6q5uF1ev8z44JK}CR@2!hMf^)sv(@`o z12~LGEjfqCSkT+kWZ1G9su)Zy<2<*rGg$BKcI1 z(@Vx}c8;xxaiQiGO{okm9t~a=xuP_tE0U>5#FUkU_7SHkOUhIQlUl5KwTsS}8{4`; zx`vkY<58&u#umC)3^~>HA_pcxjDtg4gckDf<>dOSpgzUP1CDrx|>62ssGIm~7COhC<(FwZ6X|9|DEu+tCVw;`H{`FWmo<1iv z#X$$RZ_|hnq=Gk89MHIOkEPhARCmeeI)v66I%b$QL=I##sMA8bLg>g{W>T(gN&(?l z8zy%qxJ{#7?=WJ}tVGM%EJ-Q}uye{>Ynk}|$Dyk^&WrI6p$ffmxhzXVA$Uqh>X(s; zE*zQ&k7(dq-JndsE*qBkF{ehDN>hTtX<9LX<(wW{m(Gv=IC-qHf@$BP>!mxt%G>xG zpzR*CFDr=n4bx%2By#XQR}moQABYhYWJxS{5GkjlM<@qDk9Y&E*NH3 ztl4_bZiIIIVmX@0Xthi6JF~A(2q!AqV=0*8xwzlEdv=%i+Jb%r3x`LUv6O>lm~;L@ zJL2C0iJ#8s7pUfrgTU`2P+<~3<$CN>h3;?Ekq?e$V={#6RdT78c_A1Z8r45ktBv)oV zM0&M_$aS5z7F9IH_>d4Rf^A0ZjT$n=ASYjVS>k2d;x^4&#lQ%ANJMB)E(R;T;4JLq z&re$gdF_5@sk0o_*COi`rWOJ9Sv(8ZpPU_>+^Pv?+N(u5abH1wY^Guu-b7btur%jq zP8#B)+}-8bN9D0bX4bhOzgSrGvO2rGaO;o10P^%?PP&=Ce~Gf>gko)^q7?bHkS~Rl ztVGD5$~j~Dc=idXAFm)H1oFGuw+JWcJb~kDn?1l+o28sfY&FAAI62Z)y}RLiF)oy&m+80 zfoi8zH?^hexX_)X%LD&=Zsw}2{}+>Y!_R}y=F3sP6y7y_#nGT2V?~6FwUyG+gs1Od z8PiQo3?LdRXudH)<-t!`R77LCDt(}lB=`X6^p^3#suUpdKK-Nbq3+3ZKIhJ^%HC*= zi}p6eo&re)3;6a`N29mA(DMXYw~7xD6AE*tw_7|fR7A5FP=9D$SSXY znl}ei)f{Gc$HV4BAao8-T_p2uYL5FjHm2zCO0J&?qn|D@;BgqoXix}eh5FXnkAkk8 z-s~`x+4B^|@$eB!;COJC*xEW_90uU#YbsS|b?KBE3cYc4lW_@rAWo-?di;QaK^Ca? zXQh=5ERIKH%X16fG!aiRs@nA^aVp^_}WJigI-eJ#(z{V)-n^ns=0&(dkRR#=Kmzc%hJ4t3EgdP!whmgy$U zZ6Cc^s0ql$)e-#rv4vpb@zn{=h-xO|Fa;0XK0c=`(y=-=nn>`xwivCKB_~CgsQ?x_ zQBM(#*3xez)S)Ul1D6_!5OZV_;BO*{Vi}LNoh9)FV!IRUh=93>@))8|llg6YxBxNs zZv#gr@2`yzi0X~x^VSZ*j2alnSJ>c;XrVR(Rrs0O_i97kKJ3{Sc5AHoC~MSHo#dl# ziITZa%y}{CNG>ojTOJb#TRQ3Af9pam1qe0o0?Ug8#?wWRQh@U zAOji7SEe2h|4v^gt@YFFf6!5)?@VT^4~c=detYZ*AlxjgoG1)+@-4~t@$1sX0^ zQZwejVDg!t*LhUbhMlvXa+w1X(F0eTRruN8vh_}pN**_R!f7x7e>$<{`DzrN=In>( zGs3-Wm4hWj=5Q}E4(*}(!2Zuad_2CzFxsew(a2|1s4OZ%5jbT12OkBN7Ak(P-s$}L z3TA-r7e)oJlx=pb9v`~`x)V!n-2HFQEu}9kZ@dyk2vxj4SzInIrrFgk%t!_{6p%^I z*pC>-jf_x;gd{gvb{$nPt3{3j17ly2^BU?|6=#?Y1QWCKrWu`Le|*Z6=AB_OF)%@> zC2nQ7V6WT5H7%&pDh(;voP_#J=B0dqO03e{+6doNx?oK^+3qW21s7j?E@M9Dhpav& z%~|1PSAN|V&>h>d)|4MdtR`hdev&i%!H-#_$9;S~{Mu&8lw@`j@O8@+o4af=-fIIrHs2IhHuB!AC?gd+XKkEVtoVIL33GFM^i32npY|Y~fJde-&pR7>f@M z{#AehPSVF|GN`8XAIi!VkS7{Goh=jF1O5HZ8OW&iP0g=_uBqTdZO16~6{=f7O`KcVaWC`lv{Ro<`yF}ZkS+i7c7bX7}2-1Rg zmgv74LWgrQl`HQ1beJ0i6D0VNn`NO-NXS?iGvZFTKLDo&HAJ=I`YB~JS1P4fQOT*E z*dD~htJs9A&;fv(DC))Zy*W*fR%3l?6aD`Zn5vSI5>e8IQ4hX`3>hUPoC5sZr|)6F zu^4LvP90NU!$bGNKwwXnZA417`M@?zRUt+Bj`wcyPyesl9{-tWSVm& zWaOkJfedG$wt?C~Esu&N=niK>t?B=XT=~+&Yc;L#s93m@3NcSU=KXgaBRW0&SNPHO zn7;4Y7UvIFsJ9S+X`5L*y8ktC@{E)yMaV=Tm(IuS`x)W)Z~sy9h?Hb>`FLQsjZSev zN4ZM9hl&_WY4-e$bKv!GHfB4a!@~LSeZMwJsRr$Wh9rRW|A=Ugxo8~&S}rtY#qSHu zL{)N=pgXzj?SCpWO1+&QT*E0j)DgX{v{QQiPnF&>xkx}q%yYy)sR5@7X=Z|?28X+% z5IQJW$`M;RsQ{U^&BLwiovwf`!Qf!5c+PiZM0nMcr-OR&CxM5Qun1%DnU z@p&I!RC)(3`)=%YfBN76g+PE5ad5B8-Q-i z;b>O{RkzyC1OTii|0$zTNb_nMn?Uwcl>H$Ym;4ozXuv8gaMU%il+`OM3<-cb1~Mma6$M-?&ygJgAX z&=8-Ar&8)Tgt9*~E3;qTayb$@=!gBkv;ZdxIN*TM72HdaWvvSNx;eyLf^D3JUZytX zwk`VCb%xcWk)y(`ctS=Y7SChcTOoW8sF>vmYW)=4AM73rhLqF=hd@;7kJ! zI0lV4PRWAiI1H=CxZebjdU!W72r&l+FA@zmGVCnM7X6z@G_?^Qq2``a_v8u!(6E*U-OX6f z%?gQi2LG8A^r0COhh516r37r-rq_ufMwNrxKq_NY`k3p8x5PIF60aug{@RYX%qN4 z-6=klyB3HoON9A@-Pwdg8lT)xb>jEayr}pw4`B$;Wd>!GCTH@dLJ31nmgOuBkOS zJHm99{MF+;Wq=ZM9?~uS$RwO}(2KdDlz9zGC`hjGfKDs5#cF9ff61dwH(O!QsJe?f zEH@;UBMgVh__ zfKMM-7WSPLy6jI6CIo6o`E?fO|JE5=ZM4&~u_=?HeSr3li9rJt6&EW~V}YyWvK6qf zv8~tYtrw!s?vNudye}&?7(Ys?G>FqS=H#7rKYP{n>_KDFyi%gsjT?Q}X)dnz_ivUcw`wx%AhOS6)K;DFRyX4JUOfaQnibSr%b|X&;Uqf!$?wK0w!7Ww zUk@zJ^l<;UTy%`67%=cp;8)CKOS~botThvxcZ8R=w-}EfH`0XRmmP(trtb|dng$FV zmW-dbPW1^-7hZOQabSV*I!q!6)QGz7&B@I@WW+L;AxL7*;plo1oRqYpJ--y%@yc;< z*1&yqq}TT5F%ZsZv)B|raex@qJ&@(~{qs?)b-Njn0T|Nqa(4J5WKDQpsdeN{6^A34qdgU+&D`BTt<)Z5k4&lFjbCP%Pld;ZDU9sUzt>d4}4kEi%IrR0vV z|4KhX(aVCRKsM(A2vIxl7r+Vd4byyc&@>^yOQ-?je?A?uZP%!FDPK7g6Eb%B5iuK* z$sl!?ZSWQ2|CH&n%U=e_qth`iqZM;Mye_PnTsoK&DGj5e>b5&;Da^rT92e_^kUsE$ z^XmnFHhYKSaYh1biZ3d7Q{*{Wb)WBQ7~307Zwh2%O>PnfJY=WAwZ=9N&2^T(&nrN^ z*E!ajI2ab_!sq{wvA2$DtM9gjgHyb?ODXOYcPVbg-QC??Tf8{MrMSDhySo*4CrGd_ zeU5zRz307S+%fW3cCz$2z3;Y@w(GB}x|icV z`h_jhseUSvr93~Y%Rzt(k_{W%H>r7*mUaW{%Pov!UAysd?#^0USu@sU`3-n8DLHl#DQ9f7x@4$t#r1Lk?|E`4H z@dGirfjdH~O7eclc^qJ38dr#gzZsa2R@^U{Ye3-rz($L_%iw+XF!ql?wJnk2(q3NC zc9Y+XRJxXTUdqI)#mItkj_~Jy0j3_~ls8Xo)T3lwx=`?mDJCpVC>n zFeUY7mp%c(#)J+{euDU`6dA-X zX3|vPS3}Tw9STuJ z&vG8I%2+*;-3*xmS zmz!63S%%n?*y)l5o`&zi`wp^x2k741egomM!~NEOcXxXLhl~?PtsHQ3--YrBb_RUFIAWw&%r3pZIwl0ALHToJtgY{D9BfoLzy< zTpFE($xPoPasH)5RB=0qvBYODLES3-qD-xdd;fyxeZ{gSfl{N>)Hf=>%G;ru>A!Uz za_XNb2}#gfiYdnbtIScfZ@flIt?9z60!jD){y+0OZhxO6f^RMa8Z@?#5Gd4ra7*PH zxQ0BIym2#ZFhD_S5YJuv7_;7_a%M=$aCg=nJl%!6n8LiLDEjd68L2 zO?&yO^I zdURyBes`GKou{G+{U16KW(d4)_G$wcYkcrnJvl8MV-ez!i@l9Uo2*FLnKMI7)Sdw= z!v_EtCvC?5nX&~urrpoyQNAc@<|bd-oG{n=<1CH7+?TOGHt**K=^y`<(H>PKO)@%z!X<3~cxef_Q1o%iSRS}KcUWmK_r>#4U(@8_*5 zM|zJO3kjtCMH@-cx#L)ZBLcTd$-BIe16%rQ9DlEBF$GA2yQ^jRnDUzm_bw0Mt;}rq z85QvlR(-nXp{~y7$24-^-Gd$K(NvKw)88FMJI`aFKl`V3ua2{`YN}h8*>$GYdRjgz zUtN4wXQRHwM(f*ENW&R|ixO*s{`|=iLh(KN{7=RlNxRWn@_ORq@Q>8~sD2?@G;pqD zWJ+2(5eZ)A&p2#xIPJ9peA_=aJ8;-TVq$~l_KbSOIhCdHQ^tfqC<>0Hfyk;kr?Z|v3e(pBZVTU{h)+OCUq(WF5FEVS9u zcn)ds^S64zz^PbN(Fb?X34D29;k|sMj{R~iSSO~vpoZm|8{s8Nlq+=Nvc=T3uIn_S%h=M-&H*dJjt&cv2QCjH`|x4jdt;&Wtg?PpZ}aIjH)ymp zueRGq(6PS#a{S9}vS;=$&#YfXL~!C4C|9+G4U+0!)}|d-7r@yZ^(E5~m2A&-uiHHu zRS@V(?iUSqPSV5T-hvuJk>4rJ^=ooYJcrd;UYEZuS)E&fa%699#^HzWb6)8 z{nAjy1%3DYid=O)XSyY0Z>tkM|1`Bl9n#W^lgm*-U#;x86Y66NOq7cFfgO<-qeBH~7c|opM z{vE{IDk-=6`;>?ESKT@2buJqJ*u&B;O*j?U>$gixKf;F$ zm{251yW9I)$Qo!`mQeC|g~2Q(cs}SpzCH8|!1!H3d0g^e-G}MFKyLbZGitIkGUmm{ zH$Q^i5*L)GN@<$6%q5+Tz$y8$WSfCKzoyPe2R-KF`P74JXDs@E$cy$J0P+KS+0shd zf1K~{jxQc{JIh7sirIC2XfR^KXEBPx36+h|GV*)ssn>RUJ6Y<|oJ>Q555Y2|NQ!7y z2j}&#Zfw}%8-NJ z1TXO#b!PO7KTp}3=h1@iOsoJs)|9gvB16h~lm!apHjFt1GH|2gm65UR>!&!KPnwc` z*4BR<005-9ix$@Ko;SZZ$-R@4PbZmt`>NB%lCDM`+A+mw_A*|j82LHoW+TG_Y=#Ox zLLLU)jltgc&7(JVoF#zj*0L$2kGK3&O|Z!^W~{vK48QW1*_<6tPa}T7T_Pbv)FUt%$g+1 zDx#0FLa?Dy@6d+<-c`pxB)0qAD7h_68{~W4c-2$v?D{`TO}2DViAngkb!6Uez4ObQ zpbCbRc<*v@iu&-~P1)HLy**{W@9e3l>1JNvWHtF;x7F+#hk;s_RkCnTEMuPiJWO;a zW*!(Ll6mufeK^4UZLM4^lkH+Ka3V>HHV%LbloM4(6afUF;lCo1^J}tAVn$+{B zbTJ-oY_(WVm7Li;O=i{=b$VfSf)G~Mgsb2h@JmboyuS+m2>%r+4gmlkE~>3_zgYF< ze(xNCCdB_8jUqRjabR9R1tuvV%bR5!?Rq*MV$bNPsc|vkZA3(Kod%OcpXJj& zvnq}5V`E{x4lHp#1b-0Cv+M`~+a&}tD;yE)Z6=@`yK0pf{)%da&39}l(zXb=Jfw=T ze;#03rb?!@8R@Pu{{9Zsf4CGRC7$1CRd0nyuc>W4noJ7IHMfWqA)%5<(yE(n0J9}@ zA7cBGB~4{GUmI>BN&K&$WB1r$;Bya1-k5p#eJj--JZmhKp~N;u$J(5LrM4EJow0vz zA0TU9e5j?xWKMx{wpKJ#{Pw!_R?+gif7JVC`1n}S#jW*w*xPB9Untt^nYunh>kF3Y zGOOT?_v6qN37=Iq$@7$L(ifI_40tB4sg_*7RAuwY&y=56S60sXd^8%D4Nu@ABlK3g zwgv_$cz8f9bJSCa!?eu4hmG((^i|9|-UEe{pqmlBKIotoBW!6HfZyF*_QJx#J{ET8 zZQB-dCV!iT(f7%$*ZV4e-=^lr>)j#2*C!@y8G6GwVLqI904f0@G{9vk%D(Gi5>^3& z_W`^L7#Rl>YAR1fVGPtdKIooTzr-{2&7`t1x=>tqtFj}m{u1b zO?172dkxcp4{Y%{?H$9Qn?clw-gmx7x^sBQuCIn&Bwu%AcQ(!0i<>gT79_|N#FI@x zKByORQ=M-nob+jH*g*zfC6>46`b8ZaERwwK6(J)dzrDR}1P5!%$t}KLp*lDVL$Gq% zD*4>6+`m3wV&ibKvv)iM`xE2hW*Dn=<@_8>E4B4TjS)>T!pC!a0%|K~i{i-0NVT%pgE-E^0>zhL5d!N{Y!3(~zSZOt z*oCwJ(uY&q3i9V!=5-Osr-;>34t@Y>+tCJ`Qo9W|Qoi)8T3m|qG=277Vl<)h=8Kud zZB5taNg*JB3|@YtSzun-AU7f0E82#wwA>6$%zf)>!+!+;2wN=|%qZaiY^*ZTkW0VeH)c!W~Ky>;85?bT3j<@%)@Y|fxnap zK^J4swWC2170`VLkFYmFODn-qr|6QrHOxpn8&KfBz6f07j1c70FZy-=9HSvX@NkJR zBQQDDx%$0gy)RxD?EH!mW{`z;qqEG=G3TgbHMNmFPKEv z?sFXJhY*VHv&2zm@aTklW_`eq9?X8bE9#t?85tRYbGujvBgV?w+0n@uO7t4BPk`y> z=5LjiH*bDslEW>CIP$NL;7CzS@OfpbQl|sd<^Oa=bz3t&rJYYIe?@114u8M`4hO36 z&rYpM+qG3TOZo#mX5hAbJw*%*@~;+)vNw{D{P{mJnQXc1Ych0#mM~D^u2celuv)0x z_!(OJdXDtLj!4F4<2CQXwQbNU_{PuW5m5ay%f14o(+sBj^S>FO0j@ti_?kyjJT}L4(-xPshA8~sTp@*clYx4$F6CEH2BJus~ejN{J3&8c5&~- zL(+*V_r&QH4fcMHj>1;?}CRY4pE5T$6~M7P21^$@%of0J=BNxKr9R zHCEv5&VHN;eP{QXhl{Na{g>~8 zZzIfySG2poOnzmPYfv1YVV(eTItnZUUM~WxnVWhi!QG9@lil3hP6`hUy(*$>ZRxl+ znh%6dwBC7pwSdj`1ZpzkP7Um;{9fy=r_*$69PaR4r7EPI6sUW(!fUCnZ>UqTCV5p6 za(6S5zhzNkzsUa(r^Q5Q*u@cKq|&&5G_)RFoF*MhYh5txoc1a4`btOoy}6^!$YB6@ zX1yUz|6{X;&H)?TT`|Cn7WZIXj0|8^d^20Zo5%8kTH`g&-ZF@`7^ieUgf06tio; z(Ds)$n(%N!?so46%{Fr`iNR>8CDC%}pj>qL%GJ6lC?$&eDl)=j3% zu+0{Q7Hn#HNc;<*@*Df+3I0x$qSXlb=(vUkn zzsAIvZ?4TAJ6_&U_3;}^aIs>i{CqlW$%31Tgw~XF$oSE_7=AIfq~Ct#`=vNb7n*vs zhbHEl9lx_yEwKQvR>n3w=o}p@wEEfm6Rb6Ugfs|+>S6`4X^aOp8r#k-KN7zPOH8WG z8>yxzyr#F7*knSPUB!I?NBwhWTD3K&s>%!nCvycMcWs0;ndAE>wB#Uj*%Eq~yN-9R zhyFolf5P`Esx&C&h{^~VD*J^JW^lhOW9_$@(}}(#T)Ic%5&L7s^q8@5LjYaIXfr^6q@=K}f#7DnH)wksSXgDLy;T&Z;iccl@Pypg` zdir~R8Yg8F(XonaGFD)3kn+%iGEN#XFy*i|(T%=YTJR2&DIutk;Uk6ruva+(mIj~x_HGYuK zx%IjGw;G<|9!PlLxUv0W?gRkP%bg4jExV#1IU-?rGEss%3my&M(`zj8r2UP7B~~!7 zo$}w8%c%$w3^ok(?Jv2~N0rypLWA~EW8d$$6Z`%XQ!2<++KfEkOnLUbQ@`vrh3-ze zVCsYNJgh#g`?nX2VIO;@A--nbsooo=ha!yxCZ`ygWMW+vbBJ9MG?SG2Cv`z@xWXna95&Z7cf zmTiHhbZfa~(WA!g62+L+gEJoPLQO{v$*MVH0KKoudPen^b*)7If<6GiD-%NF%oHK; z%a^W~KjvRDUKgtT>ulJ;@R1Huk(T_b7yY}m-IXzGm~@s6lJWLV1wB&+*%GzJn&Kmn zf2vO^-IzdrJprtkS9TN?9WJY&6cGH>f`O|RgTb=haZL8Km2=-|od($Hh9Up}*Ju3v z{6H5<{XZu-#CS$kKB<}71%Mpw>eQUMV+cTXX(RQP+=Lx9iJq^~G`L&$L?Zx~Ba<{H zv#srU_zp)_O0*t)C4kgdVU@u|yBzar0atymiH1l8-)ytMVq`D`{Z5_qQNymxl8>&i z;?<)d52Inm_UwRCh^gJXgY9U+6_0GeJRKt7BI_=CDPd5a!24n!^^rvk2Kna03haq> zsAY+vH9bkvaRV*;ex}D&!&jU664>X^iU)b~SVq6EvV}0XF67dq)2~;f2~R}jOL#w# z(_wPJY5c9)A_sxZGXt@Sokd5>>b{5(QDCeIf7?eNSN62H5Ogi-M$s=JEY5%^V!S+K5{J_9FoG=nFgo+E^My(YA8gkoGb0rDtYYAGG zkXj9xE+`6-_5b+yq%$6s;lmpLE3%d@!tE==E`L9Cv1()y01E~2K^AMmhxDd4A?RkvImKID=wlZ zr>VYna(vzW{tPe4DlpIqDyR=KsIQPX8|^S|cBDXQL1z(R)i|V+tmkcd?!?fWS6RA&g)O0U1D)|BEurrsq6P9PaG}hXH3qYG9XJjB>-5Q7pca_%6KH3JJ z8L=!enclXNs!I@{^yQR~z3!9^NekayK+)(m5{4;KSe6Zs(O4wMo^kTkl;ig#;6nm- zs^rHdBkx=fcF7!_H8-R4IACA_)d=g=>?1>yX+vfP=LQP5!Ref1U-8i+GlfX$3Ct=~ z073;yhkJX3Ua!EYyQs(%Mh4$N8#o12J^QyO!kV7mML`zZxh$6viIj9CfC1)~V9Kjx z421QbIG|8s^=&+-C+}2WH@VI?p3{D09QWnODL?r*t@gda!>sRoU0DRCRB_ZWpl-8lRc)tN2i?zoGh(1pX|~+6r7QR z>E==sD9-n&_3rgKb*OV30}rr_LVVHQ8B|sE11M{qz3ghawj?FcwJDNUg8?71-@BfK zkTSN?Xq*0(w!`!E;H(4(t~|+wt-)1!dXutA^R)s^kjg6o4IotY^ZtG$jQ8ya#!ocM z6`y?%!wLkbA5|L9oe6|{S!>J31YL`YPM7wY?%vUW;w=v1!3-E`Ielwm?T1v$uWM^c zf-f@Vj!0hQy%RBNRD_FUyJJC)7Z5+rQ*dpUwC|1!ls>>(*rsT35lTe?+tPK*h)R6S zoxM3>rWu=VxNw_S0~cq3^a|-@W$bwAOGta>N%surKdn~~KA~kQ$Kt;t;-sj@lCw?aD{W`@@dX0k-d z8E7?#IXI{4cPwMMupsd8UoLFQE|}1`g#&b zB(2{k2_d+LFdj8+;+6FXx!bI6u(c+!zG(_~*Cx^=2?Lfwk~U6h#?DSnl8w2}$dId* zYnF!zORce#g|tG2zc7O`g`HUX(*;Yvhu11;E;JON7Ug{c2;h?~MkP=Og8ryVZ;BTBR#+Z^lHL7>46(#cj77^vDsp@Mb zwA*jQOtkVgpjQL6cfSU!DSwKN*Q4qGO7)$(^f?i8D=fN@LFVx@&f@$QgB(|4y>ohH zPAMvPNa1JhyNxMZ4mx8#HL2DR5q>~@6PqQB;PUZ_RdAa3b@i5(SK=0C7kldQJoS(L ztb0(Wq}b+69qMz5p&-A6whRh}OKptA01!5II%h#X8L6WFSoosAJJSjYFg*D?l4wW4 zZLln(k~G=*>)>L)kh(Quu=MOtcgoWl(qG`tJWykMB)kgX$4wvbK?WZOr@XQy>9mHQ z-L*XKMAiq34;q%I0@p9c+W)}wjpaLLj^$}#kqDf6TMpzG7B$^U=49#qhJRRl4!qQ>|! z*UrV$w+JcrsX}#Mw|OO&uvcmDsba#xN;Xw@ep0t)a27*z|5Y5&?{;GF#YWIoKUT!067HDV(2Q$~YEay77n??`MUAa*+;9h*SgEJxegUVox zs*DHH5QasxBPf2UNRH<>KK`DTR+Xe}mf%m{9c}xtCgSJER)=t%YUb|L+1@_Euy=!~ z@9*|!f=#NEGM%U_J7Oe0?&%N1d0HW8iw{XYz=tw9X#==(U-vfPhJs)lT@OI0PT_4a z?(^}|d!}kx`dgx#2bSXL63lviitg$pN!M%+E;hfN>qk=I-rOb(kisiV(FlCE#_Q+< zYbLP|$TH3(2vX?&G^YLIjN1+qXUO1(m7xzAN@yF_w-2aZYDDiZUPYQ#c90O=o11Ub zr&XLB9FwLSp~2mSxg{`U03rD3x6qGJ^!VmsVu-9G0_x;gw*d#ZxaSeSSwms#2qDpp ziC~F!J$n{=l`EPA+BS{8vQaKAlCwF!W-a|Wn>k$#_p?qc?=YrbSX#+aqiamRPeg+P zOz;@3$sZWZ9JY|-o3d( zPYYBmU(SkiiWLX|(A8ZSQT|ZUv_TtNWng9*?nxVh&dh7-*eQ#2=h0_gqAE{%cmRS! zF|QD!V8SKQ8bjhHX|p5X_&Ql{>Ex>2^H1;wp(T&qn7;a{+k(yLs{}lRL3bjA@mEoc zO-8pu1lD^2u2;hZ_oLP3Wg>~4-5UiWrV~nI?4Vx;D0;OCq|&A}Jtu<%L-_XPF6`rQ ztuX1~QC|6u@mu(|%k~v**=ako92c0RfNoId&20-&Rr}k`X6DV*RI|~0uPY2(tQPa7s(-9q`-C|60{VxIbFvpye(_67%V!Sqtj6-6` zZtCNtAZ-_?AF+{jbqTu|IBj^fvh?sPpo-|bTL(wI{exrEDcERfIeH-Ut%1izsHP<* zDLGcJk+BLgid9g89&aL2qHtOKgX@wmuVYiaC?cTG&)PDax9G6~uTT&O-En2-mb;{V z+C<4g%y5*cY+LABlOA^sgQdw;9v-`4F=mRp!0KpxG4RaaUo66mI&Qr&K;8d*syQ^? z-UZI_^Q@cS)HeY|{`*kyS_ki>>71YIUOQijV^Ph$o}wz;3{ zIJkImq~PEaowiYW-2awcN)+9C9}(VQyce7)L87 zA$knKFXhC!HU5+;AMdWCXohm=Qb0hYy{g8CFoE9$F<6iF{YR%X%m6xKZKE%?^c4 z2KRQH1W2ntIvU~(NhE!{S|iZOJw13QW=M`!(f)ZiK|%%bClD_C9VlM(5zu@wGjjUr zRj2$HtfzwWLPwDI1S>h9_FnD>GSI>6n}e;C9Vs}Tjxy>bPcl@Yb(NQo$rSn-msNu_ zK&E#kt=qOT1*ZPK;Pnb-jUCSuwY}OSmoP+E9&QAE#7R?C+;9)t5&mrn(UD*8is$!E3*~1 zl#*(cXi6Zl#+ReB^-DhwGslf^pY5@O*TknS`oDuNNRuvUU^UqJ z;v*T2rLwPoMsU70DE%^w4P5ZAt}|0|@4HWe8Ww%_)xu`<=~4*?2UcX5pfhSWoloXeHM6B6M$m{`QJ7ps< zd+DKmSk}@q)>`%@v#$uKexUE+v&KVtJCWkU0QH*52pCvY^?C7 zHui*l)*@pbZS|HLsQ>hLGWYl-jYgcE$T~*|`gkx%uyFkr^9h9M^)N7Sn3X@&&~q*N zG{~>@C&}&T^=m_#k;5|5%tWj}7ZX(F*5E{s8Yxqk6PW})Xf+NwOHq4{;LWYTb(J3c zwTEl12TAShX!|#Z4rw`-m5BH*-}o(C%!NhN!G4Xl!j9|U2HJ>$B8aH@5cGz6>%JGw zPVM*Y?Dz&Gv%>*F^ChGO>?Co|LGDKzpWO}eS>s^8N9AQN8#HEM6Ri#BV zxM;*kFaYrAlAkNLuB`BA8J>Sr5S6F=;I2H-TdkXe~{>TqxpSaA+s{x+3nhwOMn4b<5v+2l4*A_`ZSa=#2SAa#pL0E z1o&!iu2hkcTSlidWTWy#w=2-vozFo6)YNn*8iVVpnRtn}L{_SbY9L%%Ctto$snVQ& z^2vCT-Th{-yztuY4HWe-{9*_u0;RXCYLhjlAR`FU;E zJwWYfEet8}#u3Quu7_D*gzr07AozMQE4>s1UOKV}#(mSVA-7jxuRDyCF?^r;wRb#W zH7lgHfKDL>+X_#|6sgEe_mZIo#NgQX?m%wjt&ASLZ0--BGO^Qd$$mCkX>LJeBdYLi zrO=!ft^4pJ+SOmfvr4K4bLWH1q|PQ;pcgJ%rxiO3x4?l zlBWA#jM$pXaD!BXaNgW24tTHyzuP)MdYk75x#l$u7-q2l931%W!Hp_tt*E`w(Y|Ku zHsXEHgXPb*LBM*cKwkzuuGRU**`Yq*YGZB7PhdvaQ|^0cYo4p z(qpjE=gqAP2K>_Yfyc$y^&EVOcZ?h=U_ndu}+*GZi_uR zeiZxo>&Ia-rwY`9>6W3vR)N+QUxOa|Ncy1y6g^JorY@wJ|DiHsZ=C;y%IJTPS!rh+UYH5%W2QcJ>Uu1V z$8L7ij>yihp!oiQM{_8D$17OEV%myICQNPhj`U%+Lmr${WP;Q2EEJK#@ZC^FYL)iJ z2_bEt{Pk<=bJ*qE7%YH!-t2W-w!!d@2bY+JiQ`@*_ec%thy+`06s{W zvl11Esc~eIr$h+doXP2eXqTTu==-trTTO^eZ3K|qHtx~Twz9=IL!_67J+9O&o6q3C6 zqf2a#Sem5afk#?0cD{H%R3Quo1T+8ay*Vs0w(k2$ZN_;*yQhrV;2((;0#^V4Y9B|Q z|7ZZC;f|rrn>WcL|8{>7028lrGhUR`8!)b!hY@*$ey^t3*?Ppm|B=A{OF`!~8PXFE%pqR+d|JvldQ zd0gx%Bx?t`h)p8<(nWZYJcF#hX#dShv%mU}dq=A8+CxVn*km+q=l`6F0 zQ5GqZ!TupGc|tt~XR(wy;4z}%l6OS-q9u9)5P)oDsjt6f@p-(v-=b|ZPk|{}npw#D z`+V=Vq*BJ#M!6OP08nsjZEJH%yz&7V0q{ z|BL`PJtC*2Vr(6!M=dFvmP*>RllebXD*zx>$wgI%$Fod6)Ur}O2Ae+peCzq?KF)*< z|A;|T*_Yb#Kk1}mtc3UmcccOk&)S?-XdNFy`A@nUnAkCBX2@>{Es%T~^w0r;cBk3x zWMtD5SyODq(b#OI){QCt9tja6EFa8Day9-~D5Na8jux5ae<*B&I^g?tZN%IzIwGRm zG$;zrLYUlcR$MYzBJsUq1yC@)ob3kbIUphGFX%f;*+2vGt9KmrZ9G!7XMiy)JXmw1 z^eO_rp*Td~Uk@9+IXeoX$)AIR{tg5EU5;PC@Kj1|y7R#ede&4-La>u%O(K#RC(8Co zqKfnQ7o$`gN7W**9@Sg?(|^-kS$JTY3-?hOntok#>tEp}Jb=6Zd;=?(uF_opRLw~E zE=D)7F1*(%TsC$+ljvw#pa6>37x_C=k{NAJy?a1)uR)7Sp#-r!Wj64IIpXsDH9;Dd zw5&Sii!On@PQ{V|Gj##xG=HyXMrz}v{ku(HLJ$MFI*tq5hSS}E~!Il(`|C#4!L?kXLDiZEHY zB84t0rJ83Lz7o1)tYJk{6H$ltjh@roCPZ(9u|-*_I?VUa;wdgz3VvK(nZ(QV%J#!w z#e)uxv0nMXZQ*HMPX}&XGTY=A4;=tU?DMunkV!6~DL@d@-nEt-oU}7IORyBVrYfTmdWcef zPW=MPI^6y}QG-l%0bKfbV&%399s7R~Znvho!loonNZQ$Z)9!*zPnKsZ;!;(ZMtKRM zmS}h;~+a z+E!37w~8}5!_va?;ye4oz3+R^Yh#4-L(?+{BsDeICx}Qzld#~d32rJ?JulXM9$gRP zZ(y%~qBug!+ciQBES3G<=m=~6+0Ui;3wPxz5xCG>va%q}UVDvj_fazKccD6ct&a%g zO5V#2$2Hejd!u+_B0BbP1)X|y0%^LE(UNTLJ=#*w+M4&gF-aMSryn7~EX_3NVtb$e z_@`cxY3(wdQhLb|5H|N)DjJEst?kD2G_$kOd(Jd>gl#$AA=Iz~k`#QkM4}cWkxHp* zur7O&F2ZN{C{#zYS$Zz|d^LsLE1S&rrE;Yu6*neh!cnoo(fBGQ>*PixlWlbTQyKR4 z3cQ$fS>$jKH<^@U87!UkSAN^*PiMGj#29THpXQf-Rh?JbpDaO6eWC4@x^J#`ou|N| z_Vmt&=l@zAotCa8o~ing zeVmEgHiXEEA>0Tk&M#1B;nGD8#J94ySVDXuH`+1I?}N%Xg33EsO~VdH>E<#3p`eWi zY@1Iw-Xq3D;rTAIDtgPrwkyyddVi1ZB$Hd%82-XDD|KJIH$qUBH1f-iOi_4zu{Jp_ zwC1ETVojBwe9NmJS$?fnT-mGPD_bXwRT6!6GyG8Q=#a=0*N0geikS8U43l05=Sal6 z8zf{zWRfTSYE^y9g?bb?FdReC{*=V9vG{O6tYE4acQWMdKQaC_U3xgs--{xEl7RhC6{*#n48MS7>P0%ILeD!VDcLb7U1XpB2EbUX}6#xRrR{_Z(j8&M6vd{XG?R- zfQa16LN{|xUTv^VmzjzbZyGVyK2}qF!PH-z05z4Qq{-}2Oz|!&^A=*M znYKMWK}-?3S=AY&$**b6{SCr;Wi~Br=LYr?qAMHTSa~bhPtzF*ZkLXIr9sA5gF9lYSbu&2-It*#%)$4>G0x;6EK9} zXxroW&WsS$DmM@OpD@9p+e9X(B27K~hiMn301L;kcXJZZWj+OKA~%VF|1X@u>%U%o zJlXWHwp2YnueF02aK8GW-FE)7>o9Z|$LjmR!{Babyt95Xy(@MvB9f8!@2*BcO=gXU zD?9n6oR}VGhua-y9Kx^2HH1D(8q&1dzcIu}zYjMEB#Imo6P$vMw3gcU(tn0?=@ICU z$yO(;C9N}_t9|2x+q!CCBDsGU&+@0!w)u+}7-g$~Oam(s_CB7V3(58GgK0wzdE6xlUp;BX=6ETtcIde!hTYR8V?Z{o!f2iwGaTL22Fo1Wpc^?Ey>n5 z>@f>doqDL~*k8dTgZtNvOiQN|@9DKJl?(!g9qAFZI)EL)aW6G1(0m#`XMJTv;yK#S z6sEsJytu9{r5#0Q8ct0&=AcK@taFn|Sf6|TD;MC4F6RFuOz}D)%HfqTv5`Y^X=r3F z{*3HtUQ3Kz>7jnd9kd-+%OEwK?tW%s_%QqkE;{+2FhVoq{q-2XaSOyiV?1?wa(Gx; zh|tfs1vZQYi+%+dVMwx74T_DnPeu4aD1`%{E$l^kjIUls;R|7wCafKc_K{6k`hs3o z^-1jWe3e)iyzREu;=XI4$G@3S!IK4#L_zGoSlWLLqfNmR=x1m{+4uDP?rBvuL2mf- zI+(dHe9O{`t}P}ypTUF+t({NiZsikY5gr-6}g<}wNlX35FoCVM4 zqoD#eK+c-jr3b~VhVq=~{}ltt!Lg+p=YG+_{C`J38^3u22zT6Vu8C;dAH{Cbc0MdS z!OT!cewgEBofp?k@UB|hi%L-RxwKhKXo@@CxiqI^;PNj}7Tx`ZP60dCf)(5hG1K$S zyHk3;q+Gx2KXTNjO-7|Ca zyU>H6s?luQf*|_yb9#qZ3#UD9WBaCMA@{~QaRcb?w715=WIKeMmI62uZcLm@<%|vH z7u6-Y-yOdB?mo36=+*_g3`854E`RIfKD+|=T_?l?7_Sf}?i`xLh~lqf5l*pQeGvd* zr=at=mq`MBOg9Ub9q7+rwK(*-^)$+Q0XuYyotK~CS`Ill#Q2UZi$6e4K)?sU-4$Cwjg*?hG67mB(zgBP)YUvD3i%T z)z;UUZxe6V2ciQT7!B|ATdd_#wB8%HIo28|@6+AXJwEpED>^$ow<<&#KSL{W? z=hW9GAtTMLOuv?-_8LrUYiSCax_|=yRD)k7Lsa5^NUIN9rtkruK(%90@aKlLjO3of*SesoRZ65gFBh@od)dFaC-Zd90gctgzd}m>kd>%>=TqlqA#R9{jjN?^@9jgx5U}2hqfQygBJN@;pe$VCvS(r9>Ev-5Ej@V39$0qm<97;qtTKoOQ zsq>?1++V1F&DT4x)Eah5!z8O#0j8m)wm?$o0Ai1;Cw+UD7(&x08?Ku-J8cxT2m%>< z9VR<vbFt2nv1n0=K{l0Aae%Dxespt<5O{RF?z0!zrHfXKl zksrR%6Ozwl^1zmav)R1GaVLAH2t1Fq`W_@2 zpEuTe@etjp+$!hc6e2XIFG_n^|^$H~%A!1)Izo`snKIkkO zcE9+m^y->P45IA9l1q%%rUS$%{)M7{s_PuH~q0$_fFk=9Go4p_wwQ(>i zuP%qvyj4M7=q)Nm*kEy!sW*MtIDyFOAV&pMt8EGvmoimK`mh@=*_9UO$=DfaaXXn{ zCG}dacO6W!;S`i~X{R|*TON+J28{~r;Wi44#wbdOB9Eu(QVdnb`lDNLF4H+K(XkPTOD8~U-z1!jRQkivWDwI7$uDs@5}7SLat;nu zr_^Va9$=^IYYTmHX$n|1G&KmT8LXUDM8}Y~9ZnNBpLaMP#4$+igo;U&6SqjSf7<7C z%BN=GW1DCyvI_U^fuAL_>Vd;ZY4Uo$z6-_Y!LY&$ts!9^Pko7rB&MOJS}Z7vDur|x zNp`m3hI+lYRoNi08;02hQ~>v+Z?roN@f7_>GR^;?5$`5OOvOChOH`}w7rh;xnjsu* zu64RIhnW|*T^)We>vCp};^(Vm=GcGPE;rguNK7Sh{nL@suBbm#l#O{;xq1Kg<`C0) zvF@K)H3K!h!QuY^^dKmGn$4D{ncdnV8C+D&e!WC&@XBd3Z}+8^Hz%Xfb>jF$_By_-@qQ!{D5O-!?6`N>cc)d&is##nLu7n8e&#m@(Di}>_)q* z34(ylH&j{mSRwbPDk@|-Qtm+ihzwyMt)#EB#L2;yYq9~tx(wnoez^(Gmg5I|!n~XP zf3&>?Ok8chFS@YeP^`F>wrKIod{sX6vS6$OFq71_BI<7Ik3MS zez40PTeBW_hi;S+2KCZG=6W&t9Qqu zlMkJdIc{voBrXwOn6E`l)^Y85z9;h{^EPUs4_zu@gHqZPF>zChS6$(eqfaiUI-RjqHdC+33Qq{MEd&*vpG5LH+Mxldb4S!s zm<0u2N}XzJcMMwXvQ_C$;^l$=Nt^shfdGZygdL^mSK$N490|q=#-rclC|;|?=X1=& zJv04-z9aX|?#*9iCfOoG|4jb$A2dLKC=_%nN7XaViLrc@qt2gLLV2#XBaY$H*B>uJ^v&LnGJ}bhetTmgoWj^$O``O z=a-8&RwD9$X?UOGg>z$XP&JN*rN}tGX1c`@{!6bL#a;>#Qj>U1?b2WG9Aypr@d{IF z=uIwispMMqe9FlT1dUIW)}P~umoZ>IJN;b+7JvOufTwj(f^k-prr;%?1)B)P z2yEo&?nKi`dc$lnsG0$y`PhYX-HxY{>Bzd@YXbWhiM3y~isf6BmYtYbYIokZROr^` zRaYw_0+uq$(PZ@}JeNatqoiG6Iy{1Z7tn6vZ`Gy?a+---jTJ=J37B$&^j}N^8{+$7_khoDc-vnL>0hIDIeL1^JiY zjegd(KtD{CRaHwdKif96!WK2PO&m%Q(pHr;_@xw}7uFu%gh%~By5EY8*Ql78ek-bUrQW2`x;VX(Pr0Dp%Mtw zqgv{@E`eb;ZfW5fOJEBt4KhUlCjR0qs^(8}X;lZ`kzyoVes(BSs)c(%EDlJb189c# z==)L3lnJsQ!V=b-kNo)Ti2G%VD4<#oJ5*kB+wJ+F%WAkS$pHhEFX$5Pj~CfeQXVoD zz%4P~ruvhj*CG8rEO9}vVJ`^$CgJ}j%S=+SPiRz4@J){-+U-&mV*xI0M>Q375)IvLPT*WLY*evz%csup5VOB6I9M6i){ z77%hCza3*@-3WEmGcw^|d04aJb+#I`+K{H`q{Dxccs)!5FNChF&EqJ}{ssa42-gg4 z+o?B`V^pcpURHvll!i)1h;<2`F|&o=9EbU*QSk~lNK*{zm^{iM*{j|?EJ~tQmpc@jS}@4#1ER)vk;hw<=xT2LmRjwexwqXHjznnx7t4Fu z{K>)=uae7I_#R~sPQ@8flemP!?cLjcqXL0IBg6D@+0ICmG%H-!9IlAdqfA|EWNUMqq4c zawk*8n7ge1ROg@wjDF<59mr{L$=W)8MY_zyvQ%FGdm1`l8*O5Vvet$x7I)v7vqS|| zkxm5Bg|(KaUKTk~Rf`*$y&u#hZvX0k&f}n6_2PwsKO8SXrwls`qD#|@N0t6kDC{*$ zc(v$KeunimEpIw6CvTNMXknvAP47Eq)m{-%adSc3E0vt)D2pn0hx>31tye&*;+A>v z)+K^YrW-{k@J2vmZQ*Fzb@O7_@keGv1v@k`lmr50>UH8b+y;sVF3^)3?W#b(`_A*` zv7I3~VRTKepveO9D2E1Qn%RLq`U!Cg(^%QKpjf9HqOH%^+g}Xze{?I(sXEJ0TfPUc zmi`SK434ZI0&?6pwH+ZbIN8hpM^cp4_UKQx17P1k!pxC?U;E19zE7j1cvwn`d^S{h zW@aX6ZO_T@*FGIgS-&+)nLSmn)u9q~b6vuLsInKznm)I7ka` zYPpMZc7tqE*$-3GRQYuzq*jTAriW6`1823K3KYwKU!{Q+zB5F!jwp(GeZjDGckbN? zJ6fD?N>F>lY!!F>2V*-9L#eeS@3Ax%bb@?kZt)U3qoh~oL!~dpBoFwr z_BX@ptE3O0^1H*}7nUS*%GYlI6;;EhN;>m<_-PJb4e=KT^FDUnconVtUuJ%Ux~cK> zA$N3mul%{+k!$|pNw{Nnty=qH_`fTo94vp5(f%E;OQigZ5_pewL!i&cZyEE=55~vn zXxFOf^xy;}U7WT^{|KKeYtYfLfpWZ1CS|RBw~Hv4ws{QpdP;i_hJj|Nki zMP*DxS=G_dQCYS1P@}&1{@VHBdj9n2Wd5r7!BKTrNjW7oCDpWmZ7k$hVF=0q2|ODN z77Ug!e2>T0@eN`&bbICZ>tsC`JAJ{u4DX?b`{>xTtnkxj_GAPXtwaMSxkX|q0^tC( zf=X_^kk!|@Yl%-##PWMHDPU1HZ)Mn?(!Bk?fA?N^CoMheh|Oi&;$dV}O-wL5XL`Gu zP04wN0`EI+q$fxHTA;l14Y$6kO&6+myXWn~iiV-y>v~}dzI_HAP!;fo%bC@)!Yfr# zZ-z4}9m{%b@z(4W<|MQ`c^;f)xk@H=jFXg5IjK0{^d0@MuA17)r0;=bU=T@VudPOQ zsP3@Yij-Stjla(K=SlPB)%@5mzjV@)FU-YOC67(V#X~aiJQkKsE*v}gkB?e6M)(#d zf*JYwj}d75@8Bi&Xa5RB6~H%bug-XpM6BFzTPy|#pd|5h#qT6Br%gBOvS#Zq{o!!- z1OjPP1i+sL?{0$VKvNjU-hFSvPrePP?AR z6-sJN5W+_je$+wGx%B73xcZjorA&1B&s77^au`0@%%V;3cNf^FSZd_0ub%$z7=uHu z@;*Y-b>sNs!+++hhvPTF5ADo-LP1aqc9(z5Cez=oG2f@xfBkhx(7iI(|Jp0}Ky!Cm zF2k$$D~! zN?zANx-dd29mRJeYTr&9cZ2;dDD_v=0Mp1ngT6 zUMlG<++2McT^MaFJFv3%9@Nm_z^`-4_{I5ni}#{I?(L==4g*N2oIa(&UO2xvmIhvj z41H8j!abxe7%>ywZM>*QJa%!T+jR>`cl*)+ok34r$EWwy_n61bJ+JqBzUlV2SApT2 zG#yzDnZ{LXYirbCun}@OTbfXk#I!jqpv%I-Xp?&H-t<5S=!#oDEYf@3XmmJ3ngzce ztiA*v`N7^473}MB`&LM+waEq+WMCKdQyGDxCB8;Ue9iRoV3~6INZfGtUU2n|$N~M0 zr^0oQqML1(%KNY4#InlfA4Izy2+hDVhOM<9-rqm|ye8wJWRSK~h922!Aq*%u)r|JX zQNNUwXsJAZbOuR3hP2>wB;gVY^33ifXG$&_FJWzZiiY4qmx^R*VKF8ucsMH!~A zSiXOLsI}o-L8lr_Tmxi$Z=otAcA--IbhV&rd&9cmYAZ{c)!rTXFNY$L<5CRPqWlOJOmK z!lW1odx-|4m&Ou1-_rpevdHv2SS}nR(Kh`(D|qvWc8PBdC^G+2()aG z{zvAttxe2IHPP!)CIic;l=Xw6W**mjT}i z%KLHGqthxDCqk=}hg>0qHJ$;a7EAXc;7O)kW%3 zQdrBMjh)ADRb!1OY{#ojGt=To&a2w7g+$a>u`3N+lDE^g#-Jz#Hod3)wNmV&d*@T~ zaQ=)+YLLMo9o-}m%Yo@T-TB7Q;HKas3szTCGsk+&x2UEj`%B;FH+;!vZaIIiTUV76 z9RMBUEF~qi?y(*D3+h^>mJ?{{^62C+H0LBG?L`eo+N-JK<<6n+Za`unwd9wT6;bHq-2 zO6c`b!0Cz|%8|7!d0Q{R@`{xa^js)m0!W1^kANL$OBkkZ)7KZKe`bh{h0~Xf(A3i0 z#JLq0pUUi51B;Q+Og`0!&XH;M(hm;{NzQNgL@D62Fx28N3ubCot79jss1=#qjflW5 z3&F@ff|Z4XLwsh2&whV()l*o zl91-zz&XqG>3!b0bdr>gmO9zpN%&7%jrHig@WP+XpmFx75taNjLF#-qARTyHu> z7HB;Hg}mmq899bpJU%8#5$Rqan29z@ z#4ZgFu8!ukj;o&f{ri;MqB=GdwpSD8h+5{4{6BNF_fr`xGqv!a(Kef6f;vhx)8bbbF5Q#xDz@?`W zya?K@Y%FUVCM5hA8G*4OKPj)@ksB7oh=@l-G9sFMQdoXcuLsz0lK>rIOV`t_gWGoj zT0>C269w%DRe*>6`kmp4=QRHg-;81B!Z$_rH+TM9G?hr0^Xc3r6}mtol-q25W~0mC zjMvenK-6>B5!tRlt;h#8+Ha*{L(9bfqZi#cb#GqHKVFqSf)Da zOh>egn=tEdy?eZbu4;8dptYh44)qLM$Ov=g|6@?n8}viZ7R#S! zYzJK>Yi9K*b|l@*O`d`JH5op1?PEw3Cp4%ViC$Pz0oRE2x{Fa8+2rOSrKuA&WdI@2 zmbJ4|DQL?h3XHDUy!tGw%c(>x3mO*-0BpHG4ENVLgg^SXa19CcH|(U8G$`_L5c#QD zB&LC!116*~uW=M_qpd%XYw(PnJhU+J^76BIi)RAdVz1oO#*e`*tIpOKJ||^G(*?Vg zCu;{Jr-9r&!x$trxO?CwrK%`Yr?|SJ7T0H4gpQ&nk3Xp9!g@^$@2iOSCE~#;JD=X+))y7vm)If&^mm>+(phjA_x#ZO zBam?CV>z_+L2-p>Jl+EnMbp3blP5bsq-3rsv6+rnMB6rGRz^z2m?gD*Y}xO96WxER zDn>>UNl%;perq|Nv%G`>?HgZ8z$Xg_SO8nxdMWXPc)|4tMH@|aiXH z-5%sYS<5#SpSxA1xpy2ExcQ!ODvem3np+$5dk^x+wH^F^-{uiU*MIu-&6T%+8a}Zy?#Z@Pg=Q(_pdRY3b{Ba zzrEF|q@y>GadXn%Y>Id-Wnh_wj+`5QvlDzST`P`B0Dk~HEXRGQphoj2;)nY{kaWeMyXqRy2gbdt|ItI7l=lf)&3*iBxp9XP`qQ9Yr@h6GJlcTt9_)ETfz_>vjGWq^woe zSBVZgJV4Webnc3d2n1HmM<@Nr@06)ThZP>+kvObBap_E>@u~dJFD>FO1gG6VYjClJ zN@OBV2GJhgYagD&_|+l8617!&!-&>Zu}o0CG~JuCMaJb2?dj>YsTs@c&LYU{+s5dm zFYmS}(}b6czF{m88bnu(58nKV2|*x9axJ{D3Upf#P9MU7uJ|h#OT(_VZFPy${pAj; z8r`DlD4VvGGqSs`64ZRl=|j`#64cVrU=7Sazo2j-L(?5A3aB8 z9T1Wj%tW|R&BQDW?DhN`)Pz!Z*GjrwU}$Z+YtQqL1%>eW3HfQr<^nP1%k$R~QlG4f z3uE4`hTKm&&zlasqnK*7bre(_Gg1Risk0yP+do5Ila!iL6V`+4rv)6+n*0WBqX^ea)k=C|`!KJPcoOKJJ|%@7K$ z+I*XQaW$_qGr6G^yL+V!tmYC}z+Q>x$)8yz*Xk>18ePK#4LIIt(Q-#o4IFlj@Hdir z)Mu?@Fzbd^wRE8|9A%;aNP&J^TlyK=I!=`qrtG@r!M~u3?0a=AQlZ)#MDa4*w-%=6 z2RWj)N7Zny8^0SLXMKmfh>dI;H_}ze|9{<-+86C1jWT@K=oj_v4hKEs<(t$C&{>qKTRT1LMiKEJ&k zk6=Iy>29y>VtD1Dz|{~AUF`7du$&emDw1^o1Y97V*f74+^zwyNB+$9;6Afv4jLoi zwW?w-+wN?muFAeciHJhBZ1n9@P?rsg?fvm1V5O+NoJ8xh!QL;!H&mc*^|Pt<7HqYr z18UXa#2&bhwH~l~nNTvO%gA0#x{u9xoN@5+5f1w1tQ*9w?_+zv?hiY!YbisH?(=Gv zNL7K$Skx>GT%~T_A2Pf@#o@kuw*Itz_K@%sQG8nx#W?hqV>j}M4iyep?t>mWaR%Vi zGnrPGKlrn#br6>XKfN}$;$ga4>zZYdP1zO{Z<``s3h3K@tDr71{ta_s+MLkWf#>Ru z8e@j5#_(l2qR!hV3UeQxx@Q=-wtHn_0T26Z1U-K*==tljreKhB-zUd&yIako7nENaR#sG(6d1+802XEqPMrMjX4&+&AKFLi z)d(@XYwpLhG>KgP>;=o9tD4UT6#oV5y zN?Dip?!;Xx?P`U$7YDNS$}4n^SDf`k^QC70Yh31_nj4@^!|7MTldT-?+p zJMO>00zA_vRwS61D&Eatx19R`Y~-}qf;R=K&Z=hAR%KsFbdpJXgn24MV7IsLHmr0l z-jodM*D)w_>KErp$ud$rM)W*I8)~aP4 zP<)p?8YJJ3<}bG}=XDa!gmyGn@a5H@=x3Yct0y;9gbs8a)p#0Vj@<+)2nT+f{mEYO z&AEq)^+@Bxz8$Bsk^JE?ouC6rnBBG?e=jtWEA7wYIG;@lbgbB1CzF)Pp(lj*tV{}1 z_afSGxr05N2V&FWa8KyBCUX5(Wl-C!$B8W_uZompf!`!X}nJBuK#^q3>TeJ!Iv>fCxx z4>ndZn%MX!^W0Mst2%w?mxivGY=8`CALUC|sNdHXR#2ghV|+V7bm6jfl1|}qYVs#r zP#_*hbtgY&YRCD%b3!-PXA4|h**TZ8(7_Io%W5r*L-UGnH+;G$6jf&>gFp0wFXJj8 zZo8w^)ZiO+7GUcRgxi{oL%h?O|B*-Zj#KEZVl*pN#=M-9wzrz0QBBFAlIrYCcZTuvt@qf} z$lIWWhkLCJUPn!BnB&*X1Js@mdjmhiaI$KnDSoC~6FGb3wtm8oQi)s;s(O3T`&dRi zOHO80xrw-ggCs`=-6Mr#(mVCPJdR>O8&vf6b+4S~o6>>OJ7i(M-x2RZ56^vZX1fUh zw=%Dix1S3V^-%*flN|GM4~^s`W#@)8`V(Z=XD$$q99=sLVY+X|M}-@3sQb9Pcv$tdoNK-@uLN)+ELEu z)p%W|Vo}jO1*GA~{obM#og+~-x8$*KZ=Utd&$!~h2~*?a+K(K$F7HxK?vQi-le(!B zz}P~))8$+wRNTMnJB}T>4Qu;3GFw$XT>r{Mwt>_IRsdE=`}vtfQR?OkGko8~DqEGn z)Ownk%DCvTh1mU_ooiK@;N9q4C>1W(dp>x~jGd0$4{Up30VDuEz-C4i_>Bw`roqje zj@N+0^Yj5G@>9LlY;EC3GyoW8(q~acX}oxM^A#0fl=r;TS@LwV=U-tk!-J2%YuCuEPZjw1^$Rok{ zNV#7eVJ6VWPO-7owI?OG9&F$zz}3j@on)BXK<;w)UyZ0`)_LwvS3pAk@KvpJ*SyYj zQ_~bcY)oeyQ4<^ey7%7uxBtk(8qr@mgA7V%jB;3r*&^`mI6$HF+`MvaIJPPR#%kj% z1inKS5uHx}KQcTvC~&Y(3vri_?5(ZYtDxlXwIgPK8h*M#vZa2XhNAJTPTWSX{qWw& zS6j$AZScJA$Z9KOMJ$s*HiB8^oJcw>D=$1HA(%0HbvZ}flou&vgLwe;qL9Bk$3RBT z`A(8E!}<0-m9j;l)o*(}(C3`d@$c=*-Nn=D!h7`TdbAm{`XW^o4M>gJMXE|Fm~9Ew z=A4?1s|F6X{;ft|3A%S}J2q{gbhMQHZvJcH_F5y0TwaJx!|SYZQQgW~?dF6hqm!s4 zlB6TVpVoF><>MQX)etA3qH*5Ok&XD#=VzI6EjQTk)FY*+6v8%1QUvb!rU7=tt@q>0 zoeS_6W82Dmx4Gs49W+m%$x}u)m$&;qiUT8+uri`O!nqnd(>g>E8VF$wpEQvg@O&EO z(=}=CW#^i(I~g{wcRL2M8@-MTb*JxJ%8+5-SdtQ|IATg8Cx%8|;VDmQC~)S}@eC;y z4asWL$tmbw69Z#aaEKoR?@BllO1J%1^DODC%T@VJb{};^ zJ-4b@Iowbd8B2s$ovwS((c#oQw+9H%_R=H4Mt@#IDpDPv?zz#cu#ln&_Be^{p6IFJJY~!QmtjP+*MX{Fqt79HWYV` zr0p&%LZ=r8V|c;=@87$>S<(5YCvy8Qw)+=+E3Kfad%RlPSdtSg`^E+}A1pr`)gKR$ z->egL`UI3ndesfOJ_<&&Pn>k6DRzbUaT+}N^Qm!os;=gv?v@1=u{_6cnXRIlZ~J=~ z!g5$zaX&0NJBy!1oK5|FS6Ap_Sp!ZAju~A*qZ7T_m6~nEkIG%tBpITo?nE9H#jp&B3&Oc#|-n7vr_r=~^5 z-Sh6@TD4GX+P@zEn0C{nvb|*;nF1{&ES8WiD&wH`Em>~0ZVViA>LC^NkqnxV#Ru%6 zM5rYAA+cEz3k9LKx0k22?O1;iyQt#Ep*Wj@A=w@@ONoJgX0t%SHIp?v7w*|5;^Obz zEPltrG)A!2LJCyK+ishU0&o*+MTYL)g4Qx$T%D`qx_2x3sz#?}5dr49&at}kvv<$| z)cN&rKpnbcts@#U03juMTuV#&j-@P)7j@9N+`2wG7*fo)wBy5ar!{oqHLnH=f(4)( zM9K``PUes6{C`b-c*9FYP=Fa5%MVoiFwg-2mzrvsY8H1CO7JefQ%V<)T z7`%Oy9%2(ggKt2!X3Ttx3_VR3+&)$s=F#AZzBC5vaH8Yx4 zA2j9?&kmV5(%AG_85{pTpQc2WGIlx^Hja^%MLp))dh;g()v{1@=DNCKEqe^>vGYhI zyv12Fe*dzjwv&^Sa_B8H=U_2)kM8y0XaDz?F)HH?sB7h9sdx0!yrvsxAV<$c+Kcs* zZWOxv&T+36$_O~%D=q;k4qck5BF6jh!GVyQ-H)mHY^?Yns|mPC=faYnuT=#HF0_lM z>u1iBC_|`GLp2ZB%pahlu(WjHewUMTsA`_!JJ}bEYQM1OLu`u{{vE)II0Qn_xHdQnQwo>_@7D@(t1P*&ec6P0s{KZ#Dzhd+Z<*;e9xS5|U{H{;>pZ>P{X!xEHra673c2szWmN_9 zErs|0OR;{Fs1$(H{DjYhI8XkVS7TuJATPje?ID;&`_TPNx4o6z_{}8M$i(d+QI(Bvr5;xvXMb22NpSC*>t#6c>YU;kd)l;0B>AE~Tk%aUdeBQe4I;Oyw z3KhjA$TC4;=rgKpV%Q7*@J_q@Jc}fRT>bW*-qp@vIKw>O6FPKj2Zw}lgJHq*ZKm9h z^e?LGIpjFL0hLUir@cr9s|nLduPndPV50Om5jzQTMIx49%6Q2C6jv+kx0w#53p87y zs6W^*I3(?!)jCslo$oOWuyi99J0H<9^uRqjDi0~HBWq|BH zFk+0@sCH99RHN(UYGHsHysYPC+&e~*i$!qi*cz()i%aJlEo<-)46sT%r1y*n{y>eW zdg;WGhJg9I9TO=~xjk1F`w-Pu&cooi75jkk*r@`naW=7h5-x%3b6m z&OPeD!A1;`_PJj1OQj>4$}jLA{3q>4mwxjGr}bwZsreFeV{v3;(!WNgMrm3l*wGH> z^>-&@@3iEN_>^U9BP4Q=0RUf#>MLEe;=!OV9WcQ;pz`7b44E*U18TkHtD!VwIqlP0 zeW2$;j@FHZnq$CUO2Z-S+&XH8kc9$Vlcb}Ce?qI<%+OT6bUrl<`S6(iXpG|YyJ<_a zvCiYgH_Z3U_$4uj$ft6dlm17y&P(?zAUh57dy!;KT!7m1YhdN$ z=BF^(r)0!}$F6Rdz9xI)@~endh{n+gegPkLy?4ykW@o-QyCvsXW{JWg^ZDq@GcOdF zKY!!s0MbT&+5QMkAGMU`0)X1qY%{0|~s9^M1!l-NOh>Px!*^(AbxURVndtG=0;r zcG?>bAXwNOz#N=Doqsx{1({YaG6X`m=L=Al$Fk~iEq;bI0Cfk%{wO+&-HYwumaNR& zH#ZHY!4KU9QF~KNsr@<^o?V|sQ^VIYzfJpfM93g8y(zTTp%e5NqzoHzw|$y(^d!%O z=*7t0UyuHz!lM>2Cl`Lpz0Q8}DCTa?NK!^r>H9-;FZ@n}vsjRCMZX(!z&mK8^s7v4 zO4^twt|kJL*7^b%(65ipr`8)4|+b|oNnci?D#!G@D6jf1MLV+TJnp2fOI}V z=G==d#wVTPLo#79`NhCACwvS~7fJ5X7^skoSW<)LGi}j9kzzDkN)h&}u+r2ff-u5& z>%vvHUcH|l8a1K}xxOe6dU@B+5enUZV9ICE8qwsGx5z>bU49OXl47S;BZ1{BPf~t3w9Ay8%E?8MvQHl&j6MQL za&*Y(XJ7bvBx$hy%5p&^auE1$>)=9&38{Ng-CbXM8uqbhZDHnUBQEgn?0iozQgVB& zlNaik$kux+fDmy?c9j16cua2+n`4FBYsh%MK#RP`1ox`8-kQ^afCMK4w(pk;I}IX% z&x!o^9p1r7F9yR?C72pzFm!6k{6H%7QdQ*EKOJ%|_a&ffJzVCV`{uL>Jfz-UHY~A4 zieS`z`LGEaF2lbW>b<3uh{J{v&mUM}Q1>#vZiNVRjF{*^2I25E7?M#^mrv3kmOWDu z_llBYAKa>?Kekq5&{DONRusCX4k1!5KfPWJYYJNOw@-X+=3t0B0-k<{5r~9(6 zem<`$Rl6~&8?jAk@$xyu!bPW!B-3Nt0h+)_p(*ZXS3dZS!?L2|h{em+Q$+T;l_ur+ zUv&|qXzpVBmIG1f*Kh1iO2VaBJjXAu1QHuwztP(X#;e-ww>BK=Z%-BWzFAHJ2poH} z1lmeTn>6liFA_GA^JZp_RlRHKz*~Pf6IvDLKwF!edJM0TK&aY6P=6ALM#Kn)^RLc59GiF`3?yclEbsG=%q8f-K-{|MdrWty ztTuCP=(Jki+-%Tfj8H?O1kjRmSj2EY0~=FokM|;GoXC{;ex3Bv zz;KiAZT_ELfcUV+nuLs&8*y`Sw^})eltH^S&}SF7PA6!em-5YQ2A`8Td#R0hB9r+^ zv;B$n4U8^BuF<=TSi#FhV}>M-Tt#z}XgvCs6~+=6AdG&464V_EQ_)sltQFrtXY+fk z%!(Rt;xXAcYeZVHL?a^5H=1QF>D(3@t0dh+b^6U~mBs%D#8f;YY_8zlO04c_K+uNzUwBi4lpk%@HnA*<=^ZWOVjx=1mgPDU%tZs|lJ&h*+6e z?wm=G7^r~`R9C$m@WXB#0h3dv+Dz6Bk>e$7*EKo;0=nrR6-hOTv$`IeRNyU zXk_|9)7B!u{sIwD1AQ~Ou%EM@tvIYviMc}o>9jAHRZ@Oe(+toA&(PDAAg&KB))JSm zoUk-CbZWC%Fl?W5ZP;)@2QuV0o-2ezuB)@vX$CoQX@-pNzpap7CW!xQEj?byq`XmI zA%wCFR!f^$RtN)ZW-?qR>{POn*e9ciX}w{c`O}AbGV0n}CfnI5Si`sD3uMivy3M~H zHANPp@Ymd+-VCuEGFuAY6{ZFIhC>g3FCi)84|_kZ*Hx%e23D2|Q?*yk>fU~Lu?Wxv z&F~uW5tn9{TGFnxmBaY=BEy~*M$E3s&dOYUHZDpwRk<2<79`wqy}fsFyAA3q$nSSQ zEGKJNxazT|!2*3&Q%Em1-?#2_wy0e5D!vbg;Tti z*EjGKRlZD|ktqs`Vof+a?_?@P2s9lIN z{{Ho{VQrXxwa&ohYr77X;v>~`{%>C9E;+*S6^_Nqp4$(DWHY>|N-mw|%~Tmdoo#he zI-Xj~)g*6Yb6Xz$XoDH3#BzGJL)Ao*1qpx;9KRw?zY;zKvIMz5C^7f>_`^LSUUkcj zDM9IrowJ|AxI*JLc4%ufUoSgJ*N9*TNu-Co1_6FPT9#ql2=sRx> zoUBoNd*#w|e93|gZ8;m9j%MP!+?n!u2Q3WJCzB!|r7Jq7ej!NPzHiF2#=^ue+c^dAKN_33i+gH!*fu_A^Q2BAC0uG1CN0Z z*-$SBx1^PMDw0H4z@WR=y(;fK_-C;d9a=K}%MjC=H#bb}+5rpIy5suG2IQlwyo0?A z7%zsI-C+CLvbUFb-l#0>a+^T8SP$`4_D<#Z&}R$D9r7f_h8$+<_e;T3H^mXt{vGGo zI#0U~&2#TR~^W%zLy5FCa@p(5l$k+w@t(jm66 zP(-31!=Me1^#pwzEpcuUfR=)BaZ2E=bDo zWnre^w=>Kx{(qC2uU!XRP2SSMZvQd`Djez@k`;H~%IR2pDfMCAe27Nux1p8tc6J~< zc6twG-}3*eaJ}a=1{E*;It3&-FS{9Ms%rDa1VfmA&?BYrAE*?y45!2VA`%9RWVr*y zmvG@2fjPAe&Hr$9jelYeg4wUR!~9Z73v{sUX!M}H0($hT2PO!h77vc^P$%)|UPN+) z@mQv(L-aZGA6@e!-OMbV&sB(3Gj!$}DK({+dALFgGjlWIZ~$B3TtjTv_qS~_uG>vR zT|jS~gj%=Qe*Bkd3$U`OJZ#5^Zu`>Q+-05qG_mg>qFeZpP#wW;`n41rbHuG7sixQpkMQA`$|O9^8>HRtSk?6pi4s0 zrWqtPhTYYrGfi;3Qk}5QXZ(ce!Gr;i8vu~voIGB4+I;y;hN{_prFtqYhXXmZw)#~5 zeH%;Z&k57iM>IKU7={v?op-E%&-lwfjv&_E}!0nyzxED(FhaPo%ch;F!q$5?WE z|Nj&)-5mEfa4?nsnrgy(FR=4n>bMg?#Mt@CP=3@uKT1J5vGwmz8R%4;f}nPz&F=?E ze_b_ILD1#&Zw|rae{%LCUBEys!YJq4Kct9pVI&MtopC`ig?Z6f@6crE)!Ju_uemo| zQwpg^l5`#)_p9-5Z`Pkq&eYCr)PzM(6MCFT?ruNdTn4~}e-tNaMk&>Z>qNhm(2=fB-@?Kj-Z>kmeB zL7{DyU(Rjtgt%G0NIwYUBlPJ_9XV3faR*Wh`2|?6uV+%|Q*}g4>ysQS&gqnt9(>xq z8%%-S&3ZUzgfcF?h@{iX=Wq&PD04lpT#*-{98Y2W=1>tmX|BCR)S${}K_IjEYe5P_ z5xx7TX?Y!aw|s*cE!)2baVV&?Mxdv$ehA+bG3;+UWPuANv-<`^>O8<&1$hq42h+t{`Y@)t#DL(<(F|i$8|KW81vB zn(q?5zI=s@!0?Lz*5E;}wOBCe!PY%m( zi(AZpkw=hr7l2CeS@AQ!gRt?k2uQOW1iQDV5vuM)om@4#QkDEb5Z->|@%7k)qPB|D zBlFZsbI^ZJKag72g&Ks1&5J#_xgsq_jAkn+>9i@W@=k{8xqjJwQznISg`;Rx`|Et= zZW+!gBF~mMd|vd8B+l({$T` zxhsu97rfUY27Igp&YP`?`f(Bz+Vu4J6j$%6=iM~{KvHzIVKH_wRliSeUmH~~>YLZ5 z9-qN#BinqREMm=^jN3Go?E~VraUG;@(O#L3Sgh@C-J80|bDezX6@hLw_W=C^qJ@O{tr3!v|Iyz(X6aCq!(PfUCaX)B`yL<^vabP;|$FN&XOWpkx$egOQ zJ`T`MmS7#GqN}G7q#cp-8!_vhtv0|9;iB`tXu74)N%4_M(-?c)U2e9AykbHaFHj`B zppjb&L#9!5P>BOGp176tr{fe)DClH>KDf}1suOYPm0;)PqJxS*#TmWV3|gXh{BB() z5&EWP1@E>TDGo!KgquyM+Hlj9dEanZF)h625eLXDd8i|49 zdU|eO4e9F^q_;QKYczh}ILS#!ruwm!?{x!EVOdyGX5a&3ayqebvo)IqgQQ=j z_#OgkFEpvs1+Gk^2gvt(gVQa|5dn+&E1Kq41iw#M2UXhQ2zL7wt{a+t_SFa|IpntR zVw(iMTbVX5+-yjrFtO6FsyaW7ieHr{q1~Uesj2swPfHz|GO9f z(sTdv7iQoAoBbZs(XBUly<_K)%7+CYIAkl+Mdjl&$dI64Ohr(jWlT9W|YL8c?lC zgtgs7roJOY)~`~PT9P|$U$N)};6VBE!G&;IX!$puqww5Hdn87sg z{l-jfu!3jQZ}F1Vz6uoqQYPD$;t(0@)c*W9qG5Ugq9i7B(w+u!Fo`2DIfKZW+z;dS zN;+9Wi)a_YkIZt8y94tHt#w8DC!D<<=>jBrdJ`hfQi4`(*d%fBsi{wg(R0_zB+ms^ z&XRK<`1trD5MVc3@~b~{Jl>X#g!XRyH_D@&8P2DqMd0sZhG(<|_b(p<`<~T>}JncXtTx79a$N;OVBIVg+>SJAOd zu11y9Gt1wA4cX1qd$X>WdV{u4j!iDjwS^BW0xJ52#SPij4I8^-X>Dg^>uIyMg;a$` z$D{Q6M43|FTcvV`i+*`S4DlbR+IgofdjwN&5Y7RBVa=2nr}wxKL)?J$|LN7yrkp};EN9u(#qow_z*Bdj(@2^GE>wVjO}L-4bAF4d^|`j zs11$0z#TA983Gwgh`6sXej%O~^DRVACTldb_SfS0m54K4OT`dxrK>i#4neKe7M|U` z8psGAt{-F9TXH=^EZ%cb-G4zszWp&izR*rNxs%7hCL=Suz6|sU`sXAGoe3(7^;t2e z58p9x1&bM7Pj_C%qi4#al*(>=>4br^oc*Yih5C2Y_um?1OpjoQ=`qo<+08&9!cSp2 zRd%f-!*IhZ&7sTleEN*jU;kjl#~hw0ui|G$xK5-07nQ?0Dgk=FR(`Lp&;Rz+426J6 z0n?hxtSZM%a1}g>Y<@7>n>PBoY)OWetYs!Z`gI=-6Vs&vEsCJ$<*-T07FaUm&1bep zUr=2?zQ65u4|gr)&1;|ViK#Mw>$rXVsk&ww?aFl< zHrmLzvf*L$b$HqPE-e<^R}1!-GZB6Zq0G0kd(n|iOlDtMtu&8 z)Zx^`=M?*r^7Iajx22_=-hPqTayx64w5MG%ow)c8N5u8wBaWu?He+)Zf1nK3_zzp1 z4j)%m6OLNFQ@dllR8}GD#98vtv9-7Zl5IaD<@pLu&+{ppt%b(m)Pm0b$Iy~}y5dwt z3<9il#|hyi(w`^Qnxa*U4AR^L(8G3!0>;b}bPG$$F67o)U5~3wajRLadCjh3*Yg5T zdq+xPHNp;ho-B83ne;M!;+gfoH7@1N7S*6>gN~Hg)M(*LVYxi3-Py z<9s~zv#bu4Oa<>lFVtf9cmb5_2|31U(>ovH(Eu81rfaZM6e`qt2ms>5ujPoaNAMR8 z$#EeK+|spB_;`GWav zKnFW*cHL=P-X&;R{=uYIkIi8#u91g`1Sp0b~EjZ#;} zaP)HCD>=6M<(OQRI-HYfKUw0EW_OveU2{e2vo2^OdtkvI zvKpcVjNR$k8)Y>hZnfa4&dM|-|4FZPXj-ojq+P^X=!gDNqkuMQj46;6N8hH8 z;!X1+59r3Zu;gOkyBzdc@@}O>& zRQfu)ZF?N?qarV6pOJmQJ)vkjVd@KJW$~-VQL#MNviu*oQGKr+#?TSLHGlm&##ytA zXtcx2Cr~;a;;w8^FYgs)U# zM6GzmO82mq50a0H-5$XK;6H|!L%c-%4E?`;pR>-Hv(B61AYt(Ra~WNTT*rJMf%h2R z4XYJTOkmTf2~3WI>NF{8Q!K>#QuqZP`{u{Ma3 z`r&Dg$1jr?Bu1}L%g%hm<)=uZCrvDD+>^tU0&}c8cS&jn;2Q`1hE)j(2_8c`qCukY zv7I+tOzeplWj~mv7rGDVEv*e@Hzx6bUX)|;QNQ-x=B{U z34^^>J3bJnjf3|D4&a?Z26XO_5)uI_?P2g;0D_7Y7rzR+Bxd`ejQ_z)21v`gsc3J8 z;)o-n!4u{mI{Hspn~=zKQv3NfFa`62sWr7*m7w>N;=_g%l7jJC*il3I(bKv0_%?0T zLRsUtf}PcLEeEYvZ37u^gLE^`gN*_O1ixJx%ZKmc*B;Pt@aIPJ&N8wALzfqKW-L&v zj8GRd)T3=PH-OQvFGB%pPiNlh{|R8Drud*eca=c@{UHh*BP=}aBzmz|*(W@|T$plr6~6LZ)?NBkDe6uoM`VTH z_O|x?g-+`)fC?MZWYO?siy#~BUu9f+skmKVKVqZA_kmc$yfTn*>JUf&dP!&=*H_NC zXr3>+KCuyW11HDq%LVw!Fb!YqT&cl*ymXG3>PE+5k%Rz9btl*$SI*#wmm+kI-XL8| zbH!I>z_B0p6ZxFoNFitf1kr_=?Les~sBcYj?ELP6{Uhs{MwSPJgm$w3mqD9vpI%JH zbQA-Y3qTJroneK`=3Tos(Og*Zc+I&KUsfC9mi3)y<(}3^DJ|Lw{eLExH5rKkg8%Nv zS~>yfTL4R5-WENt8jGrFyWUo?p5Q*99G6;yfBN|y{8JcDN_nI5X?*hRY@O45g@;FT zL5t6H=E3Q~`t?jjO-pHW0dq|?HOnmRJo<{LD2k|%yu9FTx}W+_0Jve4o9io#;4Q(2 zw)H6C-msQ5x7NGge0OV(vfaz6Nceg1BRsXL3F^Z~R29+5x;JW{P!cGl$xHQ?IeY~$ zL}Ua8GRH4A9Kj)f;`03zA^z!N?0Hq&L|tksW~*NjqllxKAKczmOxFSj23X1c06^ga z3xEYol%^EDO6kfDj=Du#*DNv`62E)ImqM9x$Wxqz@Lh>G5*86M3`io!B|XU}&{=FO zo4()3a8a}$EYw`)&Y zYbqSyp9W#}jO0PXdf2l)Mlw5ZX;zf`O;1$F ze(*HugvnW^sd@-22&Rm51eDnOdoKX!lWxG+fK%&Ettk+;hbX!9i+eY^y(5ajL0!Hp zf#Yk+^98gOuH36_H*E3_5)^=!Gx)~|!!c;n&n;2UTN~esO3I?BHVEid;FO~dJU)7~ zz`qy^oSgUc{WyHJz(ZJ!(M*ixc)qmLN3&5MlJ{jn zqZo#N@6qfcPI$+Gts45hl>_ya6(d{OnSflK4bx-H;K>oeKfQ{7!Nu=Faeu=_1m+h} zw~*4_3zhfI*WHm)XK_-=pmWqsTnW&RL(D4FIKffK9qfnBH?t}>V-Xedgnu0`FkfBi zV^|+F^6}m{TOmy%gE|@>RxP)oK|U_5;U#{a;=p8x6$NjjF|A0o`z#nUZPF#5&@}CFbV? z{xJK*>T_#O7#2Ft;pdHoTAjKmM_YjyoGKkMP#ao98T^GkCSz3}<3wSCKufyLr&Ry1 zPI4FXnLG8dF!!L>hO5fShuWiUW2w&SH>!k(=A+?_e{XvinC@>wLgphx)r197-iJTg zH?lAQznxfz=X9IN*T}E)r6PtEMKhykpPM?IAo^;v&zEqB=<&PL&-=Oq)Y?!ga@P<gM%A2dbjlH_|en?yS2`|z#% zmuf18K4&IQ5P%U_PFJpgX!WtVaO695A>gAhqb-its8Crc(0rxcvQEkTUi0|ui;%C5 za~R}v;V-OZYj!|5HVmNCv<49t0!9HKsAUtYLBR6yKn4PqS47y`Dm0pJZ%10+E#Ef| zXD;rhFMTcqO^7ryw;`%`VXllBO;nV4G&WzJ`mcwpD+`5L)!d#L0Qif>+Ugkxyu2!V zKSo*(qR$?k$>WI@fDqdi>QC~`d52^Xz=%C)nhQiD_)u0sQ*n%}b%tKYmLRf`frVj% zSvjlsdQC(hwI5z(DfY=?#uB%6f!{!=JLM283!4F}k^I=(FeU-#D~jhc1rME?uXFqG zvm}p+DE@PWoxS7hmD7Qr28f4yiipji8RonAL(;IxkfI_*!R*iI+iQ3+cU`OYWKJ-y zNQnVd^<<*)gyl{FF9UkXx35vCZe~8NjiVtzr3`H`^Ee8;l@viwr^^5f>atg|Rt26P zOwnRzeRc$D^wi0@&7mr5`Ki?UtP>nK1!+n%B6M;yBfh4T6pp3x2J~7Xy;2sfe}(

x)CLFjymj((}C!|^O4+{-IVn36<(H2mt(Bl_} zIJCRO2QS7W)^uDxNPwSnJV#Bsf@WDqjo{#%vlb=2pL=%zK#(RQfwk`p$#hvWlV53c zK(TYFT51o}s>T7;0`IOIRgk&y$rRCtwe2fJi9w7i%vbEB=!|;$1)ZNyI}vi1r|9(v zf3l;%+4J-Jpqk)uOl{`3MhegBrhivKM}2F0Hse+d3n|XVb4|5mSe#K5}HF9iB zoQWBzJ+yR?<-2?P^Hb{pOOIdP5Fj`;w*DsM^fbiOG71|gsvn}wX8QiXaO|CPJJ5T3 zn3~|(s=0c*w(c#w7L>ZUN!Wxv+;}Z`c9eOTCintlfPjz1L4m2SGqzPCk>QZrXWsgF z_V&&BeymRKW!#FPh0zIJ=dl0=g!}n(U3|yn^Wq^!Xm%E=ZW=*-skR&r5t6bz9)Zr6 zUvvvG{C48Kw%zbmyx+uh3o}SI@jR^o=2>mWvM%-=AHRTIwA3H3lD{1rM%Yu=eaDt2 zcT^u?A$LF*Eyue(K{#Y+ymU4aK>gadL;{3jcb^P8Hq%5{D5{g8qFW~=TaZwScFtWD zw6D@Et*+DPd3pYV9H$BBy}OK2=O%Re7$K~VnvkvQ-WE5!*{$35HqP>l5<6~0>Uh=D ztqGwqmrnH)FAe3WjQ06bvKSYFwZUScoal2uw7_TyyL|w=%{LWHANS&~eBI|g~|a>AQk?ovpe~Pft~7w{d$1 ziJIx9&q6DA

D2DpoHTVjWR7g6sN<8bL-AeksyuH%9N9md9Bb)t(}t|N5?ePT zRc4Em$F{A!?3j0U5@-z;czNB{{P1JL+uXYKu4UqX8!hfG4=D>>v|j4}@i;8DoH3jI zy(ALgtKcdV;DT8P8~9LJiG&9a4qCdR4rdMm+Zay|TuA&tDhgks7}3dD83>?4QJJBD zz(CCWC#6HG{3R(k>Xa5`Nzuf9#^IbKHEkK|Iaa@PUM0Y zM^i0Yy?GIXufeqz-T@Z=_LPp2xV3usR3!o%9Ewy>B{s2tsjpzC{57s-p`{wa3_tDa zweIFC->xSHHCe>cul*A0VGC9;zxG!J_wklgWCB0vaT5ska?;X0(KgLu|C=g_7zh<= z0y?K{B);>if1zep#1xGu>Q9A#ZeFwGueB&+I^eOwJs_YuQ|N=(=s%Ij-hT1N|M1vc z5;>Hgjv}=jYnBqlaq6;JBw3D{F0B43Apri5+k1Z(FTz>imK_B`Tg|M_Llnz+`2U2{ z0bMuNN=K_Kg^0z)RXkC7l{Sy3DLiz}@uC_WK|E;n1YujmrHffgu9l#@{(r;OHMgjS zFgj{>;mf8z6NhWO!aTruV*J4vegp| zn9^q_nfNXq=TD9p+&L;RBRFayMmi#~d>$CWA{_?bG~JD-3UJ83!L4R!dzR|o(^V+U^I*D=p=7n3d=?^VcXf zr)+jUsv0$qQs=i=aV{YHWU`#V=7U<_RH$UJm6OP|hlA7=axi4&wHg>FtE=XAbQ?=Q z-CK$kpd*$3g%>l)b14I3UekL%5V|=eiCm^i!X|j)>+D(gUxRCB;)T6 zvhXdo8s)}vpLTGQi$0dZjyAUj%!(x+N*fGlDAHt#MoJ=`$J^_8am&QKTyaI-WY89s z8q4KL2&V6m(2Ks@p1{h!USos``ARRPC{wW-+*6;s*G@K+v|=>T$_tv}6=4{+$n z49^G4u^K^=dx$NH5aIFeSmU~lsuoDni>b#(0<(^>#^jY=ipav4nuw%4o8 zzES|ZXvfjtwE)N26Zk#)(VcM6%iV45)<%l~;+qs3*lL|7Czf)Y5uO>D7a}G>UzO1^t92%95$t|Q=X$XRM_YkQGmP%iH&kTFus^Y?nKWmduH=v{Z&>?7 zmvj80IDUAnT2!68>su!{I~9+v5uRT0w}FHNMvRI~MR(ZK>1#v6Ob(M3=a8A8Y@kIZ z@hawL``Bu(NKfBKSI^)`dHs*}DEo!W9bwS5+@qN18US{HhaeB29Us&Q>G;c-TppD} zjeUS*OWd~xZ<;lN2`WoQ7)eLvqLGgAq4Q4LiaJzpaCheYGJ?9aG>a!#`OrP|FF`>| zzmy^Xc-9k5d4~H07k(OMDzs@_@`!1TL9ZYTn0{w1hS8TrQo>PCO%yJKs0^1{2w?wp z_-GG~$42C%rumg0*z&bWV!OpHY?vkzLkXGS5wz3bqz3Z_N@sbQc|`SczpJBg8?H>lyBU5aCBw%P zThIEy#@pxYa`EA7J0oN3l<#>Bk=>0E7+{n0@dIQtDg_?+M%|`!z4DK){vpz2ydyX% z^m1;V)BXWY2JXRg!>Cg3d^;G`4@L1!65#4OYK1Gm zqN~-q+zMFza^vjYjFs{4n+L|(3B!D?GEX*Ykbpr{P8+xU6>-S~;PW2SlCyr0_W&t$ zBD^Y{Zg%os@%t8@%Vc(=%#z&fV;|e5Zt2uI+=%A&uOJImhTD0x)&CJ7_ew3<_8PELhIq>{&_kd<^He8Cg0> z9NwcP)cC$yDxBE8io;I{X_~!m( z;{T*nPHnNq&PVQtmXAblk;`T^GBZl6Bk4fhBL&7tLqrn$p539}F;EB&@_g+J&0EEi z0jaI~!nZ7RP}VcQ039E?%O;Ny_r^0vMtQ5fjxlnSO`%c0bi81-s6}W<4?I@0bhNK< z+CyD&z>zt)Y)rsu%%f|diW}lgP^$2e^@0WwGY}zBXEnd(dAE99drhO3Xc+2g`BO2O zU_svTBud9<^^gg8Mv5#3z-CT5h%pOrkn`Ac`!>>K2WPxZ_7MgiF>`!ma8TZgwaL{{ zuZ|V~>PW_2TF7N=w#9{5LcjjB&$ykcen%P(KQrO{895TN#lS#p5GJ;D*+_@ZPK({1=a~n3;_UF# z=+84ggAyjq>HNxmQL^5l49e&LK|Qru1F{4Je@4e7Y1NMUsQY3RUB}9L?xWJz`rbh2 zDlO6=>rnQCM^+i>%zB&DNA@|m5{0qd`sCEAjYDsFI54A#ey{ef*pj27Z z^uY{L0T1-X_^o*SiuyOxbmi_?`g;d{uD2f&7^$vaTyQ$gM%*bfns(No7bD~j3-Z+? z>Qm9iLxy-@;(o-y{mAxuFC3hqEiVSO^KP}~c&Cz%CoTTPBVoX*#%puh7pMz$Yl4Uk zj=;~FN*XyuW?#`0GOD<~93FR?P4yFM=nSoDqpq2 zA6*Fx1WTQoG7MM=La3=fy4ab%;}&Bbx{&a``#LJP#<7>_vRq*P_-b1hjpWGJDv9MN zsU`|p%t0Ffg{W>B6Xr4MdVkzCLYTtP-o` zeOw~%2IeH%bVZGlBC~}BQ04fHCv zGaiTeG?bdjMq*;<41)~B6FA>aB{!*asnScI(%lj?i=)gG42Ag?RPo~^Q4Jxr4WMY* zf>gQ@2uPi=L8Q}Rt*tjFfDi~ra4}+~G*wRo5tXU{sXS#}nQ+|F>ME7QKYJwEht1|c zl)g^Q$r!BB3n$F#8!0(KqXDpg6B7P4$21O+50h{k36xfxm!>uQZv>!m2y?Y~R@OSp z941$!|KXQm*u8LfES3V%u_Foj>P5am3ghSU2rk>WzW1FT&=0xC0_pdU)~E0%@*xsB zVpZ*J&AK!p>vfA!VFk7ym?%H6%8YjxLQbk`qGSzU|cLg^z6CYZC?;Nn$|_x3nXzvzdR9B($v9|ff%okb-u0`x=0 zW^gr+Pv|lN^hUf6QFV!;IcaTt#cBh!p#tN7gBTFcnVS45%0Uu0BSojdlFsORzNgp~ zk6|f18322oBE%YKE-2+)U0uzK6zPYan-`!yvtRex7Zi4@%(T-tS1!8ViOT{uTweBd z^RHhAq^@Wvu_de5ongs{-ev zO-ht^!GLz7Joc`wiNPAWE-y#bM`$yZ7I;M3mR^jP(e;1s-aIJ}3xF;A+iz;DGNcQ6 zv@Q0{#~{PAgT(#zRviR4pv-IW_uxE0zl8UygSxP!MKQY(obWTv^Q~=e@+M6B%iL#M z-+4K&%cJL8oC07bth$QB#+M=d4@Z>G>=ldMe|~<>5CUz-e&8&jeHy)4@@d(vtXmaG zpJyf`XTPz;l1GcQSSeyMEX#apAVx{xd53hL<)%!Sje&T8;jZgMl_ zR!!jMN|%>SOabOLmis%e+v>`3u0ozHhzP-ddat+H+HHj;DJp1Mou4EUVaK~nFeJp? z;^6{fU9FMsesNzWsp`o5o?qu9N|MIW4`^;{TSWlSA;ynqa1gnj0mBJws?VsMZsphl zGG$>ulH_}Z%C-FGmsE>q~ntL7Z?+GQCRUs8d04{cNTQ5+TUXe#{4s$n&)ijf3b zQ8ZWdJ1(U2MccKx;_4J~+s>L4u2uxA0AX~|#rYA&g2J?owCyj+1x3Qdv{WwDAl(EucEMKt z9`0tSh$~?dbWHtm@~B|wb>35EiPqU`3$)4YHU80h!{Pfhm({q6{B|ztvq$Cuc_B=} zuYgFi?DM&mgb~>Wd8A2)<-iNfpV3p7Kb?thLc!$rMQqESX`!bjcNpB}cOG&O@FfgcR?$_Jc2^9PY z%SC6TU`>vSNk703gxo_*%?O>41b|iUoqj@`1&`Q?D%w6gPpXMXQigW~Fk13lF}*#p z0c@*$F8-Cpa@q3I)dkGcZSC*Jj>7Y7X>KOhsl=I{?WG76R0os)nKL-&*4_PG#v3k3 zaqZ&xFn&ad>%Y|=_Rmt zUgtk7|0h7tjMkYoQc@sU0Gar6Ov09LrzwIQAPJo5d|b#I^rw!G#68)4OZsl0pxQoiJpy$WIkuBy&DbeqFwLAF4C>p$)JGe+^>w3N?#xVYWpV0%#mmi^Y;zZoeDa7@NwiK zq0)RDP3rTPk1!(yakZbFM@hVWiS)M5CG$u|2@x2N{iFU(=3G;wI3$zaC>H`5)Nub| zT+l$%APVjyV!(8Td9Md?3hUY_rEmkq{F+D8Q9={_N?VC$`wHz*c+Wtv9^Os!<6%kt zZoeAkc(@>V#kst%T1@?+Pf#c?A&C7*g{iZ(s-6EN&WQdOOw4q2@%Rtc3ifW-gBtKZ`)$7Xy1AJsdu9ZixHTGlWkn-H&0p8ecK5M-NggNDUgCi+G6!rSmyj zSZxGq%(YS{s(?Tej68iP(!(dY+5MXzS9{}Nf*|FSYnet0lC8(0*P@GWhr$x{i02*Jx#s64`@q1k~%meA8b_?;UxS66du zWV7rw2t&lz#}`nJsf{&d#CjdS>>lJ`%ThxTSJ{2@ORPx>9`y)_|9lH0$M0s|HZ28j zf@|8aLnLC{A(%Z@{fTBY4iFg*FLtk14jqDZ4fnIK# zsXWXy6yNrcqXruLzC59WT!WlkRVWje!F%oAS=Hd8B)AX*Yr<#KVI3W$scxDmu&RaD zm)m|#^+YJ3_tD>8fRnXMSu7V^rT|d!Gy!Hb<0p4iCQYa?>Yl|7m!=beqXyX#j34btHPY*LHU3UU3Lb^g{La;w&{Lggzi=o zMg~JL`n_y`_SdT{Nq^FrVGR;2qqeK%-&djv{XzM!x$?|Z_O~H$Tn_1fl@~Lj56cHT zuAkd`9!H~Gp+mZbTg%y#5L6Km{7Y$&hoJnr1lhG-cI4hRE=Sm7Afybmw#MOph9t1Z zQOaOfHCdNVeu0pg26VbOp%d>9uwWQD901W^L`=ItSlr9BnN`h@2O;r&8R7FYSPzpc%i6=-G2 z21ML}I(gj~=SWQ|yQO(ce(C0|9}6DmxbNizF5O<}*-he^W4dN-M|F&!<#@(f(qJ;a zH26BrJPy>vv!EK4pvk$)(u^JfP=oUefK%w&4D+;|CO-pT@#Z`&C5hO|7RZFYwsLz9 z9Iic=Db7m1zy)$(@5;SdKRxK_cvX$Em(V9kk+QR|aXXh#xH~#UCw}RKu92NCo>T&K z>SMD}r#IBZDu>Iwgf?UKHH5`si|mY9o5f)sozbFHJFjAf!s`<*o4$-Gt>0QNwz5x* zNOaa5dO(dLd=LaF^7gCl;);P-;uX^d-_k>{mEbB;b(2H^B-?v?>r+$G3gJaXY^6Qj z+S+MgSt4_b^6T;%DqN`{9m&@e=LoPjKq}!|LDQ>12ZV^QD|gL{A_IM83jDhTg5_9Y z-Y>MJ^_#tRlWHj_vEYgjAq31zkeC|b8cNz+~frZczRWWu{vrD48 z;Lswlxb>CROHd;44}YK?pD(VfZY08vvi?-qdE7NWW-CBGgd7oV|6BfHQK5{<+rB+_cGw-+OA5P%0D17FKt1>G@^loN1n=c+tupmIIK@s06IBd9P?pWs3QKwh)> zQnkC&MKpYTe4LLO3K~onqHWD>I;K#5a|J(iPeC^#QFp9bj_J;CFtXBex?4x&c}|zj z-C7NqP7#hV!rh&L*R{@Fr@|`?c3!_+3oHA4+&}Ft1PuIoNq3V@)m>H0a6Qr8(jON4 zcg?^asg0H#UV^>-Z6pGqAG`W;i5w*@G{nOm>?uLuE?rtqzISw%f&6EvS!{E5nfX^h zzU(JL_)$Ka@lY#ZyHz*D>KEzQf(`rMv*IUP)2;vO#8J+G__5bMN_IUSWp}5u{&3dW zeXlwr0`8kXCx7u7VF=FP)9&;1sR_Hd46!uKkb+zidZ7OLT0=F1o`r=)Jy91n7WxV@ z92{tyYO-)~c<=wmxbdifiSWM)&j6^UkyB%=J3JwDln$$rHJi(E`Z;W-&9WtzYr(t& zcb{7s49ur5pWP$Xr~!aBlf>V=s)m5Da_E3C@voJ!1;j^UE&iK^bx3lvZ1c0@YFUk* z8=))1SK-SU%`_^nWCCY~?HVX?wKOJn4g+RfHcG#h7dFOLtxJKWF)IU^ZNyiX zv+MX@x(I+1sD`>yFY-rC)c~=4TuG+@fZtz(64X3h!7(nNQet{?W=>QjSQQnsy11pj ztY8vq0_@d_+1oo!KGnn0is)a{RM$wv4Z2<@w2_jQRPYKoF_DVDgL-;Q^K9|!X0hi9 z*#W7+j-bN~8hNMK^5fI{mpI||`MEx`E;oC`gA>{4g!kJ?;u8SC_`i=!lSSH0aWySG zAg!AleLnfr!sbX0*1mixh^3+-XPc3|3HxECD<+kP#u4A|uFR2<9+#p%#{mhBk zKKG4{okaOQmo%L1*fJpnx4Z${5O%Y4AFkJ5ZK~V){+{C0)iXk%T|T5>0I69(i<1TK z)U^4ovK-ye1I8er5J($vWi8oB=kI%<=e91F7aN+L)KOc`1Q+x{!D@Z5Uv_Ys?az}#x3g7~$@Zaa5rwGXEH~of!AuszMFRk+1TkMDpP7B;cMGN= zfnp9}sHzCc#g`bi!z8pyy2@&#fxjBaqE?g^rl!0OhIf+hi}aN;g|p*ad0RY+ojMt8 z-o&UN{O10(6eDpyU}$R00@P3R7ZIQ{e^5>wABA5!UyO<$tl{Hq@1ddWZ;fV*Kt@oUdDs&Bhh=7F`s&MXKhi<-yZPE>Z5-64 z4Np313{ts?NLN-Awgfk1zU%mpF)f4EZSjc=n70oFL@V3v3kMpofkEG?B4BT5i%Lm7 zrC>-a7x%+7G@z03VUR$#J2>2f=OSgQOn$oM1pb~pPRzxg<@^mhJK3l*?z>j5yK5Zp zrG&a=me@u8DQdlK6}3U>#)_OQPGi^6+oa|KZ4L3)#hQfcnZOq-NJioc2Hw;sK~pe* zrf!_GQX`AOaup|1bqXT4b7BAMhx)^1EbT653)*0<4DTQJzd^Rswc^;{Rp>>^RE{Ckoy-wD}}&0_dQ>CfMc zN6G^q?M`F*tu5?oT*5;BxNZDqya5j*LL^{T3KCn{{WTB!bo-I zc+NDeFz6g3)EQQ}S$Qm_<)S-2d}ph&x26ZhY%NLpa#rTgR&d5<6YJJ1UAxKY{H&j- zSwC4mUqHh1Jsb*t%G75d3GPC^z`$^k#c2So5n1~O*K(U&J*upo{x+mHnY6NW zq_6$rbE!(1PxZVT#>1>xeeptMbMrlNW!*<F>9L144r_#B z=bmRd=Y=4qTrId)SD`q0UCJSIe??jsHN-mrqBX6L*LV)EjJ8VL?XpYcC>^w1yZo*G ztgVHPyED01X;{TWa{Cwud;d344Jx}R{MM5U7jH=5d$i6{zvRJiFP?~2`t_)!}Fs9sSHzcC(JTqjpSGC7<>woso`LX_9oIA2XNQdbGL9U=~}ssBB1?r z96qh#KN?^~twZch{n~dAiN^WP_R9F4^=_f$+UpE+Fh3YQu3nf87_J!Rv<*mnefNOF z6?7Ui())0D@iVYo!2m`bPpwo(E^BAGRiN%Ctm!hBv(G>F#*H(neg5EJ00!l)JRp}wuX@OMzdOd#IfVEP6R z5Gc%GM{#sDzzsVVj-R6zyHp38TyP9(u9eN$xjbzqDBq<1}D-|ktMTQ4@C|Eu5luRzvXL&p zmo_jRzxe1#YiqqvmHrP=Zw~f2P|NC^iDFVxYY!vQ8I4QJ5|kfm6iTNCQ&q?Too#Y< z+OBIc=o@{LM^4zwxOVB>v$3sQ+=Eh|{sd?g9?a}hTyAysCnw;mNa;Dg%w#I#%|l8C z#kD6Uh(U3K*`x-s6HPPpu;%)h1(f#MgK2>pk}3ih! zgWLBaA?Q@BLHPw5cnWZ$ey*O;eQ1D>zmiI*`dRf}E_^hQGw`a3yC)7#th;b3cl)pm z$W8QLhF^+Y{_E4>X$pa#yv6qp%?tIe)P#&7Lc3LpQvsI;&tRNOnHL}98b7N3*X zhJ~WH0ZAbyt8Sr@=|}E-{0MV93MACn6VW6B z&_I0BnPENz*XB@^`MP+k&-_Rx4WC1MA#PD|u0}WD@O>lxH6ZiFt&|8JfHE*iK{*l6 zw<$z?t=B*@70@6|gcvAl@cqcx3&-Egyr^P2&Ln|pgjHIqe`BksBEmz*+gU}+aZA|6 zuh*d1qLdYKeuQo0Y?(p8v{(UU`LnfAcR!3ThdBRqiIQubD&KI+`a%b)ESrPW>wc`; zP2by~he1Z;2O8@)Jpz>Vr{msS-5S+gg*!Y8K-LNxGNA*_YmNq+1Y0*AlwnJtjr0ok z!I^CbJhv7#(ax%!y)3uW0`UIoL6-v5y?X}zMEXHG(7aTk;v|7>mY>dDv9O*t`ra{E zg5foS9y6?=pm2;!lIgF!#lWruUP&pYG&$Md!?)%(U~KFEc6+P64P=t!vV|T@2q5`( zKb*d$XK~JR+?F9%C~0f#ci7Ec5-zs&~=&w0OCA1Cs)CM z9I^t()nEXS9kg89+cOapP%25EM(JL6iJI{F=TYkI?@2NVy|L-wdG5ziY5&p&0lQOI zu+2s&AZ9U53af~Wyf%N-q1*Y%swsQ`slzRTZp*6 z-il@2&t9Y}P>I~Xg!}HG3~ki?k4DS$Xa#ncr(Z&t-?o#%A12hxO03Tz|50v=cDtz$ z-_|dV{W$>cD7Rd*sablKaxTd6aXQb!EzR*pp#=9au$&OrAlc$g z0Jq232$?{EN6#l`hYP*&_CHW|i^;rRA#2Zv!aMg+Rm7C`4Za#WHKxYG{MPafbq%Rt zS^3%3&+gM*cXua?ljKjQ4>|l3g#rH|;&dc!eQjI&_WIm3I!byqLsJ1kV{08o*~waZ ze_UC`ob7yxsw(J|mwVa=6u-Qf@EbMlS>O|H-la5#qmlu!H?~QaxB% zS}V?PUFYjoR3vy@ObhP3ecF5|ttd{<4)L>8J)0HBwQN9_;ro4rO2isTDIc6MGoHMn z9Eapp*@&NkHn`k>`GJtu6cSETJfpOpgz(d%>tEzk9b=}R7&dx`zN)$Xg73Zfym7?v31IYpn< zku*QGG5e@%?zBEp46KB9R8sH={cVQqOJ3LgKYV>-U|j9G_9SW0plQSpyF+bKbYt6c^>%L%tt!6cxbqv&l3Ro?VMKD)9uxBJ_W=wU_{Cdp46ze+o$9ZAJdrxRB^kAS+ z#lNKyvD#?WK9^{vm>WT>d44Z)Qyk#gAv7$ed>qSST7!Z2C)Z z`qpP+O}Ro&{MF8oY4}Q<`)OQQaqTsgu^Mcr(Rt+zhL#@uagc?yj03{yMCa~Vsv0?q{a~*dpKn>=f`|&HAO(T_rB8 ztoZ{E;zxxAA;whx=z4S5YkQ>XQYh?{Y>vb)u%crBz4px_LQxjP`OtF>yLV?#^1;c; zTBhQMAZx{;y}SkGTr&rCH>t+_?jd-d00ltG0RBiaNMgF&{G5wxEug~C}o<@}Yfyt~@5O~W5-oLgH@s%S+ zEDSb|rnSUQ+99-J>caoEt47SPbn2wL`gUMprXWAno{r;Yh|xk{F!Uk5|0qB0XAT5x z=Ss*|S|i5Mr}Qo+NknclmuIl=GNC0Jj6N(skwMV`dZZ@<&A<+Ba@l4t+ z2Hrx%>zS!7abO>8_vEZ=dY-w+fIHJo#R2Ln_5B?`v=~%OUU$gy)cB*2Jx}mxcrVc5 z^`he>^HftJ1=92bV_U1p*?BdiT!n=E(z2>-0f>rKR+_$aylE(yv-Y!V%g7*#E}{p> zNWF1`0)a|W(wmww5v*@+(NtREu>L{2xYuQ7FL{C`G5L$_<}!=NnYDp5wMB_H6#@?5 zzatWt!mbS>^vtPH>jUUmxGOg5X`OS1f+~uz>+6NxY(h{QSA(c1x>Nk?@~Rc_k&-_D zoR;Ohqo_%w?o6A!!b7OuK#lh@?*jl5p-UHM3xH%$2*ylOMRcjz@;Z6>{wGIDO_|_; zFVaXKH`pKo!xB z>0l32wXL?BV?Nr-{?2~K{g%JReZ;`bRY8v*5hCQ)w*dT7NcF!3nItJYa2EpWPj7hV z&84&!%qgT!lJi$eEQ`PdhmvWF_wf4-J6iIRnEMnY>8-uf*`Ka0ax;0g{UYCb9k`>2 zM7+xFy^lX&+d}Oe;Ma?1n@=VdG}<6-^leN`bnC*Aq<5Jt2sIz1>+b^LskXkSeEgC1 zj~$)GxI4wQ_dMc3WKNCm$1kjqOZ$Z1aGR6aRp$gq9%EvYc9w)pX0h1kSAK@FlVZ(E zQD4FDfDz-qO5(V+3M$r#3xG^Ab}QPesWKLE{VarO7o(6EO&ufVbu!iHj12Ms0N{Q> z*Uhe&nqEsy!Z|lmaw&A#T*d8U47kbPa7!#d1u*}^W)Jl@e3O1PIfc!CN*~H2!`#Dv zu9#p!YiHjm#PyVr#@`JWuO~_Zb;?``;h?eASH~{bQlhO{eHm_<){j#qiaHO(Du>j+ zC^9v6updXfnu&cp&TbG6hC26dM*imKqOr4n<3h>2Ye}YeWHgu{nG1ZPfDIO4oIh1o zTCg>s3vAt+T6ljk)cv}wOE>8iG_WDr`wxoi=0IY;+S2`3KHHIq^Q`n38Bbi5I|P8Y z$?WZS%!mGv*5`>>D9g~*Cd$gk8;gpD;P374JxpZW9!-^e5N|6yt{hOtaHROn;2fIW z40O#RQae@e1_{j1GjMe$lU(??9}j7V++@n23Vt`T&>hf4+KAjxJ-!~#pk%hnw~iJ-vr zPQ-eIDd(+#*n8vs-1~3@L{ce1T@QLSmTG8} zsd+`T=pk&eItGyVwy?t}YvRe&$9p*J@;!4ll>RBgMltc~`Y$a&b%2CNne7Rqnok9~ z`xD4l->Ob^cY{7OA@m3QLi&I*Awb}m3Ik*s`ojx$5s3fg(t(4L{S)`Zt4vqe@$@*M zQ3nsYZit|s={p0#AMu3B=v&YI`Q|F8tQ+l-lRPveUqwa1Ly^$p^o)yiak#l(Xx;9A zwu6<#tAZ0>1S=;bR_lcwGjIv{=K22{I*#mQB%WP}YNJCukfpZ4aq|XO6RD^A2T=aL z(M~on+1O}FZUDuF3}a(JuI&-@mxsV6TyFF)-E~#ds{c{35ggGsX)2^FZiRE4>-|rB zO%)qWjL9XmCLJ1C$x1&g2m()$>*=+0d^=KzGK}VmT2xE$Kv4HY1JepSlHl$^`tC#c z=()a0<6R0*+&}yajTiJ#2T0WNXF<@2Am`9jX);~gqXcC9pCuyVTul0f)14@R#2?9P z$s8dv$0#F*o1q}kP;mBPJBMG|x;x^@;fg#YdU;AOZd(` z)FQvg;0Xo@QnDBk{S?n0VUiVb~5z+Pwy=1cg0Gkqi9X1^R*)Pw)^ivc& z$3;?Ym58E}HEl=kS~@Hu+W;PC{Kldw=P$fCQM>%@^#e0|2|9ALk=-aB)EOzd{02Wj`bIbZW{a3FrWHBuL~d^`)0F zm1A6K;@Idaq;E7iE~V8{ZwbiH!u&1VfsdekLEB$j3pdugKQlDbU5^5FjHD@)~j*%I|`d~pB&pB-Y1km zU+mzAnInHBw8vk@8O}#&qwovb{oR>=8}fjlNF%y)(%m&I)%Os9zV)VYx6Z*Cek^{$ z)zHiiZOfm7gcX)5Mh8t7lq;#QFLdPH)kr$+;+UG`0N=1^8gBqAdp~vLj>Hf_~(5($flT{ zGR_qznZi6CQsLp;;{ygorOtOo*;22h&OQyY{&jw=sN2xq@5BNw08eclzB?o-11Mul zIewYSd>uJ}2Mcc*YkRf-)H%!Jt>NVK9DY@Te@_e`O?_R&((t@8()E)bDiw7{R;D z@z@NljYZ_+_xbs|q+WDBdPhZ|IBi1rm&*Yz_47ko>5Y}x%J+GZcjsXi#l5If_X$N@ z{Oq=jj@viMjK-^}^R`MpwGNZx-Lsmh(1dZve4(IR3RHZs{l=TF3fe}DOtRX~@7MQ8 zIolD3Sa-3)75mUFPy?@&Htl>r4!mI7;*V0f-~D>JV9-QKTmfjFaL_$6R4Ca~ZHxuo ztdc%a-*noOkE(1q73R*M9p+Jt^jyF7!;+0BySGQm?H_*N-;LpUIwl>Mkco_jLlP`- zD#Cv`I+cITkw;1V*wPeQp0gkJIRM&O1aDa-dEyw8oO}$o-I}c_rydwqT3RKzd3<`U zHJKm5j=Ojcr&%S)dCh6U{ag~0Pw&-~m;oVPHgn~zCSW5~p6ixvx`E;q=?^*}QR1zh z01bd=3(m8uuZXC)hVq~Yt6tOEmL6F5D>au_Kd!i>mRBJ9xZ_7jC5qIBij_7lB_+q- zb38_Lpe|h3+q=)0;!+TwoL$LvC-<*3Efoz&Rt{<6_2{?$Y8eji!jL`OAHHZb7HL}P zxI95~ecl<+SR_(zdptBSVdx77G|C(soU37^8yauxI_3+w;M}jA-j&7+jA4KMbaUJM ziA50w#t+r6y`6R?re6&qZ8Ya7uUQe|0e>~qvx%kzg=-cty~nFnngu0kV|cCoa-37 z{zq7p*N=A1?j6_4d@HyF`~&W7O#@Eu7;Ro+@dd`81gQh`-KNyka~GxKY#Fxt!9U*f zVeY?-Ovz^%xszci*Mb@mHIeC|Af%I1D240kE*t~(nvg(SxOQ)i)kdi`yBSgnHKjNo z&udj&E0*X>WT^0M0B7( z3YP8zY}%qj%UnHO%Qu{m{-|l?pXGOVPWus`U0RlLT|P09b0%z9TYI*&q&?rA=W?P; zv+1LK9XG+(s-OFARm7mBV+L6h zQYg6=D!fL%mS!l0aJ2w*OORl|yW8$;yP&@luUfuqW>={KFBkD&9i_eZ5Z#_UxX)nh zQajr7bPLL_qVQiHA;{B3MIk=fX|ytTYo%Tydrll`RmRe=-3)(E(s-M%dqjM-NBuws$ z+lIcu=*8`13KkjE^AhyTRPq>3|= z_VfT}6VSP_rsa|8Y23ufQ1W?Gc_4u_IK%a~mjzJvW5!|0CRH5lMCCDJdyUDKwEfPi zNrgtG+wD3>)hjd$&qFsJeP6G)`i}&cd7p8qsU!8^??R2zDlX~x`*?5sSJiG2c#FTX zqplJITaJ65MOM54qWY_Eoo{1tkg#CrRtUA~>~>irj89>@?Y$Fbjt84-w|?Dci0>1Z zz4bka78KKiLQGP=zv9D7NIQ%jDuOFc+^=3(1M*P9LqYu>o{wZwRVA~KXYPerZAZ+JSuiGt8VDeHv}{ z!H&N{i^`Jd-9-5yVV#cGQ)qzY!v$RQlDo`Dn=Yqh9;fXw4gB+9?ab;`rr1B;!eG5* zdCWP=0K^WuL zA3*KoFz3;R37pqqlwN?S?DOQd3#E;M^+){ZuBdiO4Ogo%v;on=4a9+zk6S^Wlx z%sUjsuA18JEk%#rO)2wa!pSc2`+r0L{a2E+A~{t#A`$H`Y3DecmKQS<+${!&mEn{f z&ks_cA;O_m0AlV6dBZMegO^ztB;DySP%wzh8BY(!WS_34Pb1D;uD%XTa32NheBQ+h z$s4c1$F-vaS5TOF6PUoiczH=AH#nY&0Q!!*J>`@8<{Had&N8_eV=8F^B#@q~C4&P<&46+&6Jdws1j^)tfzwI)a~a&O50HSyk*W%#nX3@rg5u@Z z-{Z-IWuX(j%3yseGrHiP9AA~GkRVnTT&*&nCh=bgI@%ul!#LE!4Y4wOLT8vTj2QMx zitq~lG3LGMhfE+I2{I=Vq_mqW^IQ8X#;n!0$<5q40wXEF;pTz?{=P>6QNx=4A}l@yM0bu zjg2O83N&#ke`K!X^_yFr_%xpKr^xKRPR0lenV8jf7sC&h6(V}xUB5*{xZ4MUK+NWT zT?F0AQA+)id3Yv{QEkB*3nR?&{~QypV+nJMSLMKoh_X;fXs3b+@WR5R7j}SWi>GD{ zkbFNo3>?zW?~LTZ0bBpaKr$^IHG#H>HSxgSxcSy4s8d(CRmJmknf@W7-t|tp<2y1Iitt%J0BSNfqoB5Gr-ymeQ^9qk6Bn)1HZ>O?5t7Q$RTWMv zjl%QT39(U_jm$lx)Ke1&yWeQNW3xmgiP+XC8JMU`wS{@tzvt+hPSs#PP#*w9P7Zs- zX(Q!sW9Sb1WR>;5Cj!e^�<&xA)iCu2;zkCh@N)2W<~;hj8^v5c-%O>iZda)N)U+ zBbX05_mIPY-(XtgVqpk48BrK*-+E$Np;_XNnf;L^DUd!kt@VUYR<~;rPtKnhE~{%~ zD~TJj;906XCr@Owb5(Wd*uBikqt5L?-`B_m~{OBjP0s- zfDhp5WV(qmve5CpL4C38lkHlo?I@bW$6VtyK2qLm%-^MGay{rgO~AW0t_bt*uTRk- z5L%aSBHx1~2R52cp~X(OX}RQ(8g0%Vl+IX1nfxGGkp=*P$YOo5w_6nx@VSose$AQL zR{L-j0%e zJwzk5)kylcM0^2e&jbU}nC=X0WPdA@S!xU+!yTw-I?m1DTxaEZy@`DNeh^J-;yj|^ z1KowKa1Nd0{!Y*c8X6$8Vt+iAbbx+ha#98vFzROJpbC}l_E0dj{DWpgf!xaH0tdOSKH0Lm)~74KWt+1mQ%0an6r8&rLXJPlxE zNxxwEGa*hS8_QB6%KB3xR*tAN#>)eKp7)xRy%WhSl3HZX-BYWtp6`C_{Bp&jxv4B(I@=7O00fY~I&mB&UG?te; z0R!IIS8Iqo)(@J$_XnI~4ymP|k(0U&EM{P9Mt?B&ve~VB+NpCpw{N+)WH{A}%6vU| z+0p$83OI#xbX)bWezMLh8{l!^2)%7^wbOYnabcMoTrm$-SWdFoM?Z7h%rgV-KW-cN z=WsR=pJBJ8T06MenbCa+9?plj-VGvKP^Q9tymH83$FSq=4Ss%lP;U$ z{jB-%>H}%(yn9F*go%ureBI32+Nz#5SgB0)nPnB99~B*QBcLPt@T#%i&Q>t}?K4{UL3+aKETPb$gU558_%(C~`@YP- z5k(cUk*RivQ@ z7h|)SFFua(ESqt7?7W5us5HdSY@|=`>|gt+`Mj~II)IoXDgtRsR*Ww2B45N#aLY|i zj9@?h51uvSg>0GCbr)#@p@+F+X2j8f1V|E=U0x%n9EcVb90mwx8@Ws#c0J=4KadGf zLaH@N`#!nf-W9z~)qm3OHmAuEQmH5LjReE`634I1ZaH|Y6Umn`;?otB0l>vKKc2azNqDVJU!^h zfm+}#V3y#&T&`i}eChIxx4!f|!Z>bwSa=QRyLfyzCWBgI!IR);cEo?}tEUp`2=k{u zTZazRpA9!$*fcnwN)&@JGZec*nDse&h}a+aKKJuXsR7rKPPMPTV*#(9;K#Qp(Zn<_ zbO7LY4Wmj+?8SpbZbYj`i`wbD{SY1A+i`@DoH5ROo#@R7~3053{<1t4tNZEEF_=dWGRxm z0gEpC#bV2o%D>y~bm$;G<;9?U3A?5h)pCx`XlxRBDUXJ)r5$dgr}(?0J-4|Gr0dan zUI#}uhB}s4Php6oaz#mTS}^{eqlq_yVtT&7G*g{POwbRs$)5gwXwO-#w#$0INlgn4 z7%v$ppLOjx+%!|`X#ckSq?^;pZg?+*K|BvMUn!JaDrf8G#v`7?3hPISpp{2gWHC3L z6gGEr%{B}Eid13gvbn3-76AtNEa3W5aFOrXYLhR8v|LVI*DVbkb^$J2y^s`CjXREWnX98BP`E~y5I-m zgbQtp8kJHjxjeTX+OU#?9=T^}lv^C9*ATB<;#c~l5oiFGEXrZw=lIV!C!ANpgViNQ zGkeh3y5PY-1i{ze(l_a#0YhFh=k4fRl@*thn{D8tAJT9Q*!PP+*C1p}?5dTj{i}jV)KCw%48Ytvw&N(DrXlC#MC}?~6V}!n^ppNVPiL=>q^(L587} zX3mEzl8KVS5auhLyZ1clS}%cyEIp@@7%x|C4m!{G7VbNK&>u`^*X)K@WB7`wI1y_` z3jL}+!)>q)rLNeObx2=Ab`lR{>zy=&OFEBs77_cz5`N_c^|U$)Nz%HN7`;%eTpzyQ zuc!GX0ycS%n@SWS%s6YS3f^m}%|4N8E#o;Sg?~JVS$I3y?`T3zjDDSx;U%0e%*J|dIKAE77XbLm^oHGUyA8arR%uQy4i(fD&;O2jamSN8W}xVX)O-9HbTG*F!p- zPh-R6K@-FKaTkAO`Wwg#ek<$u^V7*PK`?B@X5+(4gpA>KPSLS$>STA_+M>ffZ ziL^#T*l;tkuZ-qvEe<+DQL5eB(NUQTM$OhYx)GYs4ecJ4qMuu@&yELNwqK$JHs0^h z?6S`fOz>A#{A_?A)}fM9rhuo(P(8k_BYSFbDo2Fb__hlII)SL|gfDn&c$zz<~a~m|ZsqtU8K~D0FbqfTo zUfNQc7$V-QCpTlDR*(*WWj{nF7kbXV~-ij_UcXrj5uTicvcqyZt1{r&yFW=>_oU5}=jiA&gTX7!n2li9t6>V!Db+2|ggK3*4J zfBGvj3kDaKB+=y;B9u>8M7XP~#SDfY?%ByF)1_$n#`kBBdgkGedMKT1rTm?5p#GGf zA@ln{t}CAoN8OX1#cVpUT>-=eN`gh#mwS=6*yo1Nx%QxoMG^29Wrs+R1 z|1ElY4m#N$cW-m365L-x(m2GDCSLBour`*|i;~3fG2q!c;X;)6S>Oies12+?hM5fH zGo*zEKe4F^B*C)AL-Gx+`yg1OoO5{0qZnM4(Uvo}S93Vb2uM zR5d%z_i7<0V0p?FCoMd|$SSL<72E8WT#n@~ml}NotTRyR;05 zmB`~}SV5`Vb!u!bSr&6Qa)0C?aaDJ*!`0reWdrVO&C_y!C|K36*cFR^q#*>lkpI+( z{5Ly$x>%)?r}u}`or6X;gx}p|pUO7RM|BeNuTo1rqb;OPX^4%BE33u$(9Pvd8cP|c zy)GVdSqCu`di>ptiDLdlS%d}BhgQA>mETyF<0Gu~)lCzF^IjX{RapGd$Z@^t+H$Sg zbvy&=xrywrLz9Jy)77S&jt-AJke+t9*?&E}exT!WdN$@ql@9&pdgm9Dt7|wWHaVm) zRu)XS$JBa0gBx`VwPYx9RiFyFRqY4u6K{0y>$sbaQ ze5)c|s-5~IQ!ik?Xu&RaGR#li>B#dPIhsY56m^8~X;F;7nC}CtnGgRG8!sAxk-psmWr25d^iD0?|&+Z9oD@IeMts!x=s%$(#$zfW8Sr zJPoP|N~U>AU$awz7;(xHx)F)dshLCNqTU(d8SQLXt_*LY6gBG#gsHoh~eDYxNc;lc*c@AV=d z9swzGu=L1vc2MzLcP%B~qZXtP@*`zbbY%vpAVKM0LXE?im8%Mr zt|C9zbcvuO87?U$mkTrs*3kyrmwA`bbe;H~OJ;6L0RhxO_N-ee zDQ7uSn2&TYPQeiYw{o&l&?+6nN`FJx7G#PA0C<(hv{*1dK@L#Dzx`V3@Ab~2g9CJV z(n?trPaWvaPr`rCPU_Zm#c`M7tD=`CMJ^VIyy!=Q%5+giMS_TV9AttPi^TgrpZNAqxf zjjQHc{+~mU9i66E!D=%n?KHgQYSVOdWQ6?DFvX^!B9m|F1aUca#VdxwX4}1_Qy<36 z;VM7UEF<48M*3NXHI8P3E}sk!8GV)+lo6@EN&K+A_ z$=D%>3(K4H7Ytb%m_+vT#>YN7i_lCLRnR0W+R}CyvHH?UH3Am#eP6dPmM#e|AR1j^Vb^DXMe`%QC~u7wJns;~ zT%EI}io_m9^bQ(kNpa!$ryq$BnLB=Sc8{?W`e*Q@Q8My!`B8A4ctbbF-~2ZD>*vu( z{Q!|<6|E8hTxVqCRLaQ?4i$f{yF1o^PQWNtovzX_Uenu2zVp%%XP=@@Tx;>W%l$W& zv${neF=Clr^oj8XS!5~s0wO5SC0%e{x{}86OG6u1L&}e8Kjk^La8%OPVnGjd9{z22 z`EiHK@zYj0@}3_p+O;KXYF6${vG*^&&Q-P@Jph3=S}_ZX@vaXyK0uQN?FyO*gWoGx zMx{bQERvQ*$?+T34J6?gd4Xf(6NhFwX6H2BUiP^fQmH4ZwVpZzJrb{Z^?kV_)L>GSAv>rtvR!<5y+Tt1->kVq ze9}Uy$q=F*v$me9p+G~0EOi`A^JDBFFkS+beK9Q9Tk)S63A^Oiw_A;EZ4Ru%U;vF@ zfaIV6$!p_$=kYzcDrH#DX!N+fkKKFrCXq?|Y7>qQ_pt5Cqduan+c-V7-e5H!Mzp>0 zNK^B{tm3M)V-Mf3AEw8dnf z9VJi0G5njZoG|&m@SL%KbGpoTetp=|Ub@501F&kFwdjNisX;Wfu{4RN6pU2Nq5GQV z)BH5-U+D=F%y<2%+l=Qqim#dvHFU+h(deWTNBgQu_0rw8@pAXced6)U`BJy2%JZbo ztsm?M#(gxEfw&DfSJFVR0UuL)`atWjKDqs+h;Vicui|2(_pE(HM|by9+OGe5HwO^C zDxpwVw-E_P>?1?E6QsKcx%arXRQtU(gct!SwA+I3zH`ylIT3M7vwlBvM1U!6Nstgo zCMpS(Mv0i%zXmzB;aLxl&Z{I@Y%Ac zs_oj2j9f0m}lidMMI`b;o`Y&UhX(6tdm^hi(5W23p=aL;lFv zV^$g2O&q0?qN=}X7Y^RRF;=~DcUGn#MH*>srS^bg+Wr-7#4qb=z+l-j*sT#nez$tT zNJ4%OhY~#NX4Os7DGq$H&v5lQGjQ)xVthD%3~Rq{fv0T~)YOhvL2w_Zwn9mZdnf?_ zSXwRcRxEMvvC92wv&mVteMz%PlZF4ynl69Yvu5Z2rOmCRlB4DGV&+K7v?1IT4%eMZ z3){9bRsUrkVPMO?5fUvECrOmJ%%H9vBnF_j_gia{cKEL6o+Qmn29FelNWo@+sak%} zICyql-kh^p_|ksbl80#H6I5JS-lH6Bw+g51H@cKi;T$SKNw+P#I{)Dp%jW~S*;XwE zE-5Ok9%rF&7(dd04ogntKBr)uufE#=fM{+coFy@+Vt4xz17?Q-43;*#WmFyo>zd5r z{`bzyH2=memls@fTPTZ}QK|wRgAV|(2&75|2e9;krGyG*AEL?IO!3lb4Sop%WE;^; zOqdoij*x7Zk@kN-8LgP8DC}l2ARCh7|HyM@?Z=44p^J+*kZqM(Ks)=5 zYYGJ|@SEv4=p1%onIIoGqhJCbLKkq8Nu|=|a)cc?m&dh(2;v?_WQ`EdoM#oN&MhI_NS(NUXKGxewCS==3+nb1^=X1I1-m z(WNyI2$I?f6VN{iZ^1*?isntkRU!>!8-z@wEQDV3*|-@}4kyb}ymF;!SJ@7vzoJj1 z+p?V;f4AUxSd=e_aElhJAi(qU+(zrb<66O>8?f5uoLa+g*PY^ znnz(hJT>|O33q*c6{-s6N0%br>((xc3<1Edys>2v@?VWUB7|Mk?Pc7t5?GGH@`Usq zKoW^a(oi%k9ozpJE}6HrxoCLX=SZb5)YK(Yl-<~9L3Z+~Z!a`P(IgJIc$8MoiOg+L zxR^Nu^DBJGh%{BdL{V$Bzdkh@eRf`I{g9E_p;7+>XbRyAXwENGK0dS3J7AW8i`leV z%*+GXXDP-hi|A!oQSX(#+YK3*W@m;lgRe)9|4f%9G+1Ke zJSZ&TGI`N*oIqe(m7xTug@#RmOV;^QI-LrZU7sqLkd*`>+9eyAXK$%D{qvsh$dOFK zZrss}`^aK;#DnEP+~yDgiav%{bEY6|u2Uz8j@CQxeWcE%&!SN0}~xNFo?4N;Gg9_4Y(N z5qW(ZL#rMmOkDR`6OaicfpRkLJ=>cu#2!Dkt@?RGv16j7oz7jQJIp6 za0IT}f`Qa1Nh9MXU10&I?YrgHrHNsuQ=-u3cB7wpRkg-$0#Sy>yBP}b z@;w7Bv}+d!_FUJ!@xjPCyY&MZOL51|&U3N#bKW^?N*2#`AX%WfgMglxm62Z09Y`KF zH|apKT0jt_no%$`6g7fUyc*5WM!b1lep{v7EncbbOrae`{QI8SbXjhta`UBVTQP*K zqmcFY{TPh@wpz`jx8)VgfE zVHRX(c?zc25KfP37N?187`U*au!A1$vWD`8)zk`98eWz4$rqY^xb1M_4nZ*agg{b{a0OeL4}7c7Dm?P z1|2se=Mr6pJty>Bz@5E^mHF0$!kTLQ{MK;Ih9%)*5 zYD2E=wShkj?mec7L=hDL=a}o({=5h^?60pg&W|3^{$XEA-T${H4*s8QoVuU${%Uk5 z(J?E1D=sxTI5kY!tTCxah1)cNp`ODI6EZFWScy4!T=Nzis>QKSIv7MIx_p5tiDJ$K z3UkfTwrOlK!-^Lo+@%_NEm14CVKZL#Cck)thw)S7lGC7u`EuTUE%88FDIj#>b5fBq zt*oF-1P>0eO3Vlx*c#INj=p7Lf<0oLy|*W|ADia$_>?S6bb4@5D|IFl9;@(^0#d+X zS%g<(ML9oFFD4Yit>H=b(O+)Wqs*n>KD)MFTg}gZLco(A^bgzZ-Y~WYPZQUo4ZgWv zvm7^ED6BG?2COs|2^g1mY2q-@621sT-Ty#L?)RO)Lb;49;DRj5pq9MMN#9|aLkaBP zSds3J@yMdO*2$`|U%-ihgOFAZ@0GNi?i||(hY~Fd=UhVn#*-M0t0GZT7LrTX-Zkqt zi+7V(G*OBZ0f?s(;)cCn-O4clycGSsPy?}n`9>&sXO#~sz1tzXPJaE)ewAfzWH~<( z{!nMdal>28upmr7&A36Nst`o;*%p zL`$!rqGCCUl>;LtavzwCWRCm*-~|t>n$3(2{J;eHi{S{H1@TjWN+(L6z?OG&J~*Ir zGi#oMWw^4PW?^H=G)NP`>*}|zIa{I&Zd|K(PM2~*@AT+z9F&u1#_9!1kf_L_=UKKJ zK*?4RB7v#{QfKm zHh<9D`F#4>u=mlF}Jfz!=hGaL1;?Ko(pXk>Oi~Vz zMJcVxXN3g|k>g!#eXVOMSw+sI*GBcrUq+S=Puz|sG|&(zkz{S2s3<;q>QMmjhSi=J z+RMlZEc9Dv_)cu*F6vO8(iLrE5Xjd8JfoANy@m-Z_yd=GO)KlNCZxeoC#abd_Mdn8 zQ{j!CT82xz;N7B%x*-6lBNYds%>Xf-L4!7YWyM8g zt?9ZCGBZhByL`d*)|YCxyZ?+6f28BZg(iKkpi%9%n-;XcF~PBJl*?VqYimG{SiXAtEIaM!%74RvKonYjzh`8F|SBaaJ_(VhTvHjejoV*dZqm z_lIVwOYZwZPtabFOLlnp%K~dt$5Y0Dzhxy`$%EdaU7%)&|>CGJ{X;=LjCL`+UE=o`7w zVa01K%MXz+MY_%DTQelu)91$Zs{uS>G2Sl@6u2?M_SrwgV755`0EKez3$6rrbrL#g zkNyI5sJV6q9+D*_;6G|&1Khb7IWQvay-HwqE)a-HaI-=PYMs-_xZuMUlT}X}I8Aa~ zO+JaXPjHmkk`JUPVd!r_6<_~5_I$$kug1uQcd@3i`kZZzz!8#KP)Y*~gT!$9GW%!S zSMpu3qZXaltOq+oaKM=`OxWV%#uuA?u&nlJb!th3Be7d=4}N15IC4cX==Ion_6gWA zJt+T(yw~Bl4V$$H0)e8|{}ccHKY-E03T{ovTLj$VNkjn2l03<8q>T4SdJoE~J|%f2 zx?9dB_uFkKsoUodd*DR;xPG2GdSV+sCWDSFZ4)W4=GnNGp#=+&cIag*yGU~ zETYM}#qIe*a$JL8aYC)UG9nl0=-@Ee04X~l7p4Ym{{weQ!;L9 z!3eFcne@TYW&|7L33nbxVRQ?vB~kdGoUGjg9Ek8d>)>yQtK6yv3UR7$gGK23%gM0` zQEI6FMms0VB8T)VKJsF&;rJ14=hDFS`%6NfF6i?={a^^O^LWSxE_-E+gAmqqiv5(z z@B=DY56@M_p58Yeof=gE1>A%%5icY-KX}A@Cx?)ow;PZDR#nT#>2Z~2_`vIQPw7me zu42Kd-MqNHAg%r3>XH;T?1<5NmYq~(9uzoc^9A<*9oof2cY#e?RK&w+b}w{r=Qg_B$mW!^^sy4dVAdjX&9jOOXu9$7+_Cr$kH8P&RB_=HL^~F+ zrbFcwW0j+YNfofz5~E3QXGvs@Y#x96D{0nQn@MR5c`zWn2IKNiMw(|4P2@V8IZBuk zv#G!T_?;4aEyC9J!&`@)mQAKhq`l&AK5~@MFQURglwU3@H35GGksOEhv%QIqo6_DI zR}S-qXxT%^J`6Upme$QDi(tRweLuqgeh*1SXQo|*m+@_Tht9FdvJj{<1n-8;nb?ye zMgbTqvon}a-2q$z^A0X1kF%pzzBv@;?X%{KOnb3uufv;q<`Ne6!su9d#Pt@w7q5;9 z_M!co0)Db@s$h2$b$FGmGr(l}nU=LU)(CZtO62#5{Cpx6HBWskaA0djtI`6i+{}qs zb?*Wc9zD`C$sAW@f_0L4+i^$xK2uk8e3w9uA2I7`POf8PAwK7+&BF zd0q^6d=)x3_wTDRi=~h%m{gN3NCeZr(0zyXuu9gKdplha23dQg{|}@fAOZwnqCZ1w z^p#nR8PJW{t2MpNY#%WX$v*z-99E{Po|=l!L+Q(6ZRr)++7i{%ahsiAN_=sH+o z@AG{6Jav%s&$Q*s%ZShN*mWqK62+3HUx7O)%K6$LHf%^QR`>l43 z5NZze>E>ha|0?XJaKcYYg#>Z~RAkg#)SyKl7X+P_r|nF){3E~eyP4~Dc$%-L$;btO z&aTsB8j-uy)Ap-|>i*Tz4h!7QfD>slN{23;tU@LsiZa*uppPqtY$XF;Oho?m%*2nH zy!tX#+GA_$D>DCseyIeZx%mICu?pudOrA=^oQP=#6st}v&7zF) zPLw`AR9d+osP1=Yn_3rS;sqI;lz{pXwaE1z+9{sZB)O!{Uw?mxM5EoB(f;?#_mA3d|9m%f zO@~8ySRhy6@_-uLAPOaY*1uFRM;j(Q)}y$|shheR6eG6m7v8kzN@wPAY>O|wU~nIm zdQ#OZ)KCn7|Kj<}=9;ASg(W*3V!vu@gRai1(*#2wAZfH-Glt(-o!yq&S9w(46CLP9 z`+8JNZ#x~X{7hHDBf)6URFF4Op4w7@B)O;ZkYq-Tq7P^PW+uRDM1_AdmtSixIW8(A zP9qJv>vV4*2Rn$~FS(B6-i-^bQrGRu?_FrX-CN8x^gl?rTT}_LU``It$uFc{FWJex z5a5E*4UO$ZT+IaooR17>5gKK-8h?RqvLLMH%$o<90lmwLlW@O!y=n_X?n_ZaTfZ-Q zXf3}a>YEFc;_|SN$1N^=RV+UJONkD4p{wda-zT9o%ECH?D|ROG+Wv{=X4zs1(9 zYHLx}JtS`)@4`CJI-_swe@y7`Ssc)w_)#dXFOnK62&$Nstbg0yC?^1^5=ASo!wo?` zDNNU;=LsN($}0FfoE+y0+9pxQauU(r;>1wFo;w@Yv}%iSBHd!)>?Q5oD54^-3$a7R zJUewJY^ZA;jZ{Ye7%s=B%8Gm`6Emtx8!ATc`0N%`NU}ITUvutaw7CnNY;08V` z_#d|K>}0l^X26JU?&k-?QUlu>Sq+cevX{pq(H{}5`!!c-~t%^sJVHEYqs$T9`A1k z#=>7p|Me!2fL;g@p{}=`i@L6kYA!Wj8B!c2F%GuTiq*8nzx8nr#d{{x0%Y|HVCBCOy^mqww88(k9YY`{|VD1Vh{Hz*ibU>_7 zTa8Oe1BweMV1tcjf*+^sK4x2=7%^0E(nYM5JV% zEAmihhv(Mo{o0oIGdKGFzCn#T4ThSkve)DOW&3A_>%;j*opL-K0*Nv<)u;ocl!^KP zHP4gn$6e@G?B}uMAO2U)p`9*c^|MSgc&RS;$*U^7tZSNk$zE#W-Cu~~pa*JtH^WHa z`a){sf`rohVMq3czw@VksaY*Z3R|z?+?6eXrrD_|$ht4L=YXgU=uu2tUN6vSKSiZ+ zY3<)91-~b<;gAmGh_A7bg8rXqfGV>=t<@0=0BB}cCG%GW5Tu-hNSS)d>;uu`?lVOy53LLpQCDF>sh(eUFK8vC`nmRua}|M=yN!2;Wn(7 z!e=i%E}q-Vst}vw;ahE$v(N8IJqm(McEM5_s-8jC!^9GF-|swl|BU;bV`lvDx7(^^ zaoH(@&K$Pxpt?)aKnw|*QvN0mg^!?|w805gH_(sboA zJftiJjQNy|NpQ`|%KE1i=eM^Q>;^^3RG&BGzQ4b{%;bEicOUZK{p&$4f+zYrw%}ub z!_voK`?k~2^)`~T)j1dMf{PFy@#uRRym2D1R52!2@qtD=hw+{C4IM`>EAjB4z_3wI zl2GLofeXw40NN@)FDg!{Nxy?0ZO})g?_+=5=ex#ZX^zqeex@>l^8%vt1P?*Is!m-Y zY|dSxHoq>DhgEC4W%+IhX~Ix~0{q51>&3^4#3UTgE<%2rvm`VC%uP8OcrJ0hN-Gov zZ)dWS%gG2+Uv$-4(!3`&-nYAyu8O-yv3nR}0fq>GVI7CML1)cGlzpp2)*a91I74rInnX~af?rfqYrJCg9-<~sz zbumE>^&kE}OG&f*?--?1#7?vG zfBS`dcuacP?a2Z~GI1asScuPIpj7q(SCQZY(Jy2d)^iBI%}&1CT=xf++uW8Zqf!d` zt%Z8#XvOUm8#8%PuFmGORNh}CBUPo!?o)W2d}#T?%cwow^qZd(ea`-oq6Aum;`Ngz#yGsXt<< zR_#wBytA*V6Wi{O#i#@i`Ml@Q1Qi=7u<)0pnH52kn&VF`>&>KOA}#z=#^3nTG-TN< zo+LDQ`Ia4;nrqEYHa|I$n(FIS6H_qJ&?Y-0_OL6yafgaA)gQL}*N(%Az}s5<|1ZmF z)w{yK$nVha?7X!l|MyR;RX(c+)tPgvms=-R;c#E{^4V?WcrJ+|Z2v2*Nf|NZzf=!% ziwKa)&c>`k65}S+)aLc4x|Uw}v1G#W1tu~$Ai$KCC{BV>L!kuJsPkacOt1!h6G#q_ zKjFy|DE7J$^X2XIMiBWaNhcgDuX^rdXDSn?I%sN8?zdBZT-3%i#9%YSO*O@GF8+Vl zG?->Mq~lq1a>#ohef5dm+Vt8T%%T%ZWKjpr_SI~ePRPj#JwGEAOV%5m%fv|{hIW}N zBZTwL*BZZc=aNp`*x=qhDGujO?f3k9OZW=o2jAfHuo=7^4u?zL$2h$49Btb4Z;hP) zqZhyDWI~Q#tF9Z1ED;jP-FsZx-3?UOLzQcS^;$h?dHGph<+QPQzLxD{JoHd^+2Lsa z@qq~H>IHV6^SQ0gZyt|aX!kxip-hDyF8X=6_1W~&+FFwG@d@hn!WVJ6{^>h8{-AT; zc~b03MC2a?5OJk$;;nJmTanv4*xE$k4YhRPeC}lQ+wYw2{bIL@LJb51hqCfOMG=QV zpI<9o(vi@rFI#v>yL}geOwwV6fZ@4J8M82%WS+vUm}D;E%{e+McKv*5tgB-N_At>j zD6PyXQ;yE^lu)(FQlkDOZSW>`&%u_U!9D-umyAZPTOYU(*81thkmN&!%QfwJmI+d|3FpXXnCP@5|XkZYL+FO;($3JA0I3(64GfZ^VH;>Iw?5z!cwY z$LGt2E${jUm_UW<=cn!aj1~STl^iAa0^LlcBFX`0t=~TwTq%Ny6c?}a{mnh#WuCMGRDusA~3C_xm?`S ztDn+MNkR$Y4eTJ0E^d2?zo?%-i_mKFT>Qq4^d4W#;mIH~qqdx#27<&+>iAcX%}dJ8 zrojPnv-3#$ifei0#j!LSq`pky;X}$qs{hf!-}wSq*YIqT{2$MFS(7gz-s@_PC;$E| z4wS&&stHGOKp0OCbDzMeFM7Zg7Fvq^K4D58sMu^t1Q(TYRWWgP4^>EZJ;^h(J}KoL z(Kcc{enroc8v$&rDe|WlRc{ zr|dqDoLcFTd{;JS7_SUHJzp_HyNrIofrIx!RDWDOEY;PuZTEBwqd`GIg$%(gvN#Cx zIu+t6#4^}l$}Oc&bvm}svTWO*nmgef2X*j!TWk(DR4WhN%Y9vEBRTUnMyJ2L+Rm`< z8&etiSJAa>lk&Wc8H|k(c8K0(@El-giwQPXt~9nrWs}{tQVgk|rxMr_!KrnK02+_r zmP)Bd8Jg$oR=X#qRG`gdqx_tu6$U6?%n)PB2u%(&FsyAWRwCcL>)0V`ke_$k20@<-fnWL6niOk*I$` z9Ta6)%hK8nxAyjO$@bN2@SQaD)85C-kzzyFci-rOd8$HpZT8-I1V%x)y!G`Qa>=@^ zu>)n1l#;%~vnfx(xEB@6=*!o>UD%i*DUEst=b5}<_yiTIBxNd4G7l^42eSU3&F`DP ze*L1Oqibnta=g7*pB!Vxj!sxLa_iO8X_3#%UNd~FTUSb>iKbid+RahJhJtElsJO&K zx2Mf&+4}Xk)#PC)_^L)M@|E_c_BKa(>jpM5n;@t5Rm>cZT@G)|oLSl?)_?ojk8nEX zmk%W!Sz%%0!}K3|m$T)I^K)8^$N__zloU(=4Ff~6V0Lm+wnVwr{r13mt?6>5A^&gv zS^j$tkHyw^e5!Jk0c~IF?2YWvzvfOg3L6UxtRAw){ic!%SLY46n5s6?qYF`#W{dSI zdyOh+>k=Ec@5YvjCwyaPjryQ7s4=+A-r)B*9#GTuzCY&moSmC{v|72@)UrK}h&a>H z9-frVR)zyY`h`cwr1IsjP*G&FId2~yH^)XtJ?`BR76iG-$a;Re|LXNk?)GMI!IT*m-pWb@2 z{cwVPVRDk+=Uydfdh6o>RIRf=<@<4)L(l{6MV{utRtVjb`Ad+1TvdZAmLr%sAK zpEaYR(~W-WIhg*rPciGcJ;)*J8L7Nr`_n5WcJI0J;J4RG?9)HDK*js@alKcwu1EyO z=-AkQ?*u+r$jFro7Lm5=SC^Nrt8JL2rDT+g^TO?q*}I;XoP+(iA7i@MzVD|EZLV$2 z&GYl~JfFSrh=?*@2V6tM2LC&GX7QZ)jx8y?m+*M6*T;P0e7fo^(Io`n@8wQWjt-9( z<3du$r@-~Hr=HBpemPdx0t5gUD0l=i@EG3EHP0-p6~!D)O#?Be~Rd|EqFpP&{z;uNIBjD+Lx7_T!%ZgP4iwCXH6> zmx>tl1|#PfxDxnhAJ#8mu%)|YLq+Sn`bvlr^mXUyRJY6bO3%)~KxIRY zk1#IHz^6|REogM1&)Q}TmYJvKo0Cy7<6@6y22&3u6hK()Y{^nJsIfdp8`izDfU@H^ z6eC9yZeLzeA}%NADo;hewe7~AnACFQ1oH56Zb*_cNFza#!w$;s+vT$$$2#EX@-01m zlyjCTgV{;T-YR{*bV`^5zcJ^k_I9#8KU=%tWD|A|viz(UY7@TtCWP5*5C8g6uk=1C zd#~$K{AtF+J;Dk~h5jGW}Nn*dS2cBP^wt2N0IWqZpc{9BD z_71@Z{TLyjkr3C7Uzh#ta;G^S9IzKco)E_tssi!S=GJ;of6xN<0!=y;SC+fA>p zpIgb%*6PaEA(-d)SD0Qz|5x9-)W%>f4N~4Ib?^e;Dl1X)sbr(gvPk^z$eF5#B5jRa z`B*fZ4XHr3o)V(<0H>`OCT>T6rg+5Y_JkxFRww~Nq6lVJy`bt@fu}l^R>LTbr_PP`KQ>O}&v0+lEMl&h*@EL9SAaE}CI%@w5)JFu+DR z73&*SU+P{@nRQ^8**l!>sFZk}S>8_Nl{~XQG9HSJI;IiwZLZuvr_SWW85sjWXt6+b z?v-{_WlVwy1gV$;-;j{zs^A;L-1TPTnrqiAyx>ZTs$st0VM2%`gLn9C`#6L|XS}Dv{FHUsP@QzW@Rp zES;a`{L1P*{1u`|m4)bZ3{Pum|K?#{_A(t^n=MdXt{7~>7kL*fAC0-cc=lyOM7Ank z{Xrn28(4}l@2`oCG-tic;M-;1CW~>YL;&B>P#q6EL9y|Tn)K(6yC(ZSb|waSQ==RT zT=BR@B;RJxa4>>n!Nss?!039<%7F3VWR@3xQ)z4K-{zoNdHihr+ryzE z8cnCY;kb+Jk*4y=V{fXtND9MT8SDbZ8Pn7;^ItF&>R!hmutF73vRwr69T}vhhQoGN*cJUn2VTLn>q~{cf~QMy0P9r zOFhB;QFOQ6=n@QEyT5fxG#IW<^=+J@vSoyQfq{M7XDfAL6{@~ISC08J46(0e1q}t& z)pkP<=bjH|&s~rFxyKR@V^hbuifSs11q$?K|FQ#6KdDkxe6b3@Y@12Ckio%8zyW5p zx#7}?EU#(_GA!SIj0O^p?)N|8uR#=v`suVsDb?sm%0sX)eI2rsEsb5r12f#E|F16F+;Y;gs=)iPEX zERNEZ$qe!B%l@%=`yv5Kid#8QMJNp6vElqK66hCVR8hgWJeo&{%LW@6YtWSfT!^QN zbo6qedh7kD`f~1dVG;Na6uJ~M(}AjzY&T3elhtPww(-SBL`65h_7A#`%`6{o zZm{Lo(^2T#0ZK|ehVHtl-Z?qBhWT&pld?7H>zk{47kK;|wFqCsQd7<1m07zy}oa6TYY`KrKRK=-#Ll`NBcD*$iYA@YPWA05p})XbR@w6 z6v#p&BN8aj2=N5b^v{X7ayB^e(AWX$mTX%mgT z_-5;=S%91`a+We(ifPK2)eMTJHZy-#jW+Vy^&mA7PY3PmhLQ&nUVu_^k;rp1Y@909 zNQX;>xdP;gW?%B50&`+}S5?eaijt|iX5vUgm)Cbv+o)0h+x%ka$*$(~w9+Iy% z2~N2S^|KyGj6IY9u&3wc5*Kc>heu8J>L)9)D2AAsJEGMjynugDFF$vYG)Rix`@XQ( z0Y)C=PQ#!9fR`rNwW_8q4*d~NcdSn8hQXQWHZ)}Mx0hCKsy%{ChC$LgHPm-?EQR7 z+a5Ry)csMIdid39Y0ISGeJf~E^t&DvOBoGVvwgnEiaW`qCZDo&`)O{uc%l90pQ!!Rq~S2 ze$$=5x46ZiUyn!rMD1$hPFd0A-S=)fyX{tE9JLX?m)1c)WOlQKi;9`STAU*K2R+ZN z)PbGt+};PNtrp&P_Q#VFcx)TJ(NPtx*@YY(3x*rsg9*p8i59mCp-_&mhWsdzn*XGl zlwsu>zBVMm43VBFexWrrw(1Bu%a!iIXDJ~ZboyhyRurPoJ&hm6qcKv#3fSXBtkM0o3H6s#p=WbBVWQ((wbeJ^{^5;rB6eS+$-s8dBi;%IX zHFcT_ZQMqfMPZ7_J`_cch|5AZD)bu~FeK6SoG*0S^6L%Y4(acwJokPcpzmHlH)3*o z+$hoJ!w*zn*O0F^v-kKDFteIX&B3+I-c%0=$jjUL_;iYbaLJAxhe_RcAWD=tXDVZU z*zlXO$xp#l%*fqO;{T-u$c*1x%(^{J;~D5E3?WEn+N#{{{c~o42NkPh2jfFZJNu}p z0}klr=QVU{ND4{S4cc!$<_qx*lL=A)LWY)(ZQ}@p8O!2A@FQff5Wunl8yHZLAVGCf zz#<^kr8IrZNQ7@B6oUwTLTTt~c;1a@1ob#f(Er-}jTMiDpM`U3k_5{(2tOt%Kz*-WwiY9T}nOhbui8D>O5o$cGs%Xp^dah6ld z7>Rw_)rCe|@70Pcp{3~P>1`FWe{cvs(%9YX1qF+=)IibqnPe1&)ZE26FwjM?SZc!3 z7G^C6ViLxY4&aS>z@q+yUeRYD+Biz_rGu9SpP(h9Ds^v~XwI|O%S6ne5U?8mncJj~ z3v`c9+7kcz%{DFi&?6fJ);-+1BK*Oi{m62FF$^0a;^{E_mOQfCU*Bz_s>*wvFTQ~U z|5ypt{)RY)7nU)QPo@k2YYv0MBNj}ZXeDd#>ur*~y@R;4Wr{-ekAmq{_B8?2;F3m0 zDHNa;^n%q>>=|S)KRRY9Im!D<1Naq@AKmBwURvlmt?ENd3^cdB{QVnbHoJ9uFD;)& zEziPQK4`?026WHNAQK7Cu}H*MR@yW)G$df_zu#bXJ*`P^vEm~=ZYdGzw6`y(rzda5 z1&#&Pg>r|UHeZ|CTH$gD`mNYlk+GDkJ{WZReO30hmsMImDbl?wrJnGX5OqCN_pHqaBPr62e?Wtw8UU ziwbGz3Gr4)%?;b+s}*E+yM`!&%2mlEc0H!uez^?Q3lQZ3x0w5Z(7mH!AUJ_@&JYPz z5aC_uw-zquK0wrCh0kKFYTt>^xM0FWOakDC_}~men_29M2O=0T7x{>jw00_AwaheA zbyw5F1*XL|UXNx-?9wc8Sd@IjuRmG}K1Fp0h4@UiHaj}=>4X{(1K@m6$pgvvV; z$VWRVhu5Rbkz-LWK{e(l%MF$E+?^j-Dfj2H*u6ia1bhN}SGC!8bbhGA-+E`__k+c% z4-Ls5K==Cw%eKWrE+TX{t?|$zWb@wW@z|`-S!T1l<5YTgCANsg>>eIYk@Ih!u(&Sn^XKj|ycy@^ zZe*k9KHBTmQlqF-?BC%Jc2sNvCz%NlE;*VNXs4Bts<|V7rc4=mM%TIH>+B?GA z#o9MsIn?MHI;s^~k}8FazxEx5aicM6L_pNnaE1cR@)|Yexm!63jC0y2`8Z#C7i|I~ z>k^cy_Q^>E^axpG3dfsGzg~&XKi^LEb$SE96c0GNt*jyjASlQ&Gb34`NP^}(yhhfQ zz5Qo;db-->Tr*A{WG1+hm^dIcm)uz2sNLdWEU~J{m-9IAN2r5p;jbuuU@lW_5mOSt z%oi{{T8TgQ#n)FXZ+G(wv*PUH4~82TkxGj}Y2OaWRGy}FUj0|Y&9gWBj`z*qiN+|b zHLZ{>in#mx*G62uw#bd}#=4b#84rZv;`y(w($q#e$$z^AFl#$J&w_$##bil6IEYk& z1F%WS$FKVRt0Rfe&#a=5{e&5>4O))lbf~u4-ET*jO@FPnr@38rNK{VZ;C-K)gXUjv z0k5b?*o@q4f9_9X&S1Blq%*ADnj$JKD^pTbYO-2$*?9^tQ>#K4gU#V}bvZd70ZsFT zGfF)kSzw$mVi26vr(zJwq$OKYE3MI!l;%t7 z%MTY`DiZFY75esjXtjhuYwOE+kd7bqR|GCzsPZ~}Be;9pvT}`?iXg~v^O@DrY#+_N zVVrbl25|>CkB0)AEim>-%>}ffq2Ry}nf(;H)jG3V46den9Yd3F?UvHo(aDnLo~u;< z;`;dyG^81M>gILUj6Q4jGU+uJpUcb35BI4ITP|5_<%_Qq$+Tj+p63l)A4~U=CW6LD z`b=q^TrO6xjpzKYD{`kG5w0X@%8;|Y!}Z;bm+h!-_z+>SG``qZYPiZd8HFlLBf)@b+VIXdL6ki3lgFVV5*F)*O4>qWARMJbq%#$>Kb;I{?F)q*LNg z!B0y{DsXXei3<0>xiLrkc)#3!?8-SAxILtX49LvP%>5U}-jyV?r4#}kCRxCBwc-yV zx1V2LxHvel5ncuh2g{Z!WjDTXIUlSsqTaXiHFQtvH|k`;qFyTLqey<~?v@^tA9AV= z;|QdqARQTxzxTNaPe>Ls3w7W9@RgMf%*atIiKQB>v&XYuiQVZ!DAGT zYdyVxzN&usUG(Zjy5I0ioZujT0fMHdKlSYnfap|y0aerdA>{v+P&#yy3x>r(K=_*Q zDEpXr_DL*QyuH*VJ3j1px@7u^U#2Ey_Tpi35G<8~;;YQ^`tTi})sd?hlvm<0+I%m) zuDbj%JipjLgoJ$X_ph_j_59bJo|@YAKK0q@X;lkpf)GP4E&ptzPwsyHACFnYN$TJA zb{qrBJ7_(ebEhTk*ITX7V5=SOS*>?y(O{61^>=;@M!M9QrR$$?@T)h-ko|Y8sGWr+ zUo9o2z;7P=S+0D1KI{Y$5H$U&q4E21Ls#K&ul;UOOIN2^URU??4W52HA_2h>buqe}lXi9hl;pWG zH#2*=xBZnOPV(o^AH{|jW$%u@G5Wp+OXivekA{bC|L8W?!_5{Y(9dsmKT4a_c-KCd z#)xNJn3};mY2;_Q~A3dN0Z)kKW5pMHMxD1vpxpBZMF7>~MM+ zX*fA&v$#8yzC!mq-yvon4yI+9q=QJI*(qI(zGvkv!!k9~R~j%AaY*l#atT_+vRW!K&QI<{R|tv>IUTV9Tz zAMeN1s;uly&)_}DJ%?};`mrS+i_UCZ<#dER4sou| zq%_j!R@NmY6j|M0P(&|Zt_$63BMI<3*SOhzl;#}6ue95(+lI2el(LAQboPX;e>rJ`;L*`orp{?10~FJ zFU_a@Axs=A(%M#Ad&|=MgShd7Kl4J%xh#6+o@N0nx_JE83)iUhU3Yya>DUt1=S#Z2 z?}G#%|3Jw~{4W3(JRBEjaC0X~?jKBK{5Mtq`xhI~D`@g($u)Y2ve+$%unj%z8vr!Z z39WKTXNc)CGQh|uu!}C(6o!34#T;Z2w~?VP2Bv5krpvY$X`UHi!*>0o@+PT3TzxTgO;EXK~UJJ07cE?Voc~%Q+wtuio&m zvg_l%>izv46bs_v;aOW-1F`Y(JG;BY7?Ht(+vjWUE$*(H@2{VlWFOfKJmEaDKS_TM z-C7b-zejD*p^YJZ!9b2?CvaNw{Vn7{yV<^|>+`-wO#9x=HYQUqpRY*O@W@TlyL*cW zrSsFqMcpWBdCK=FH#fhcKtd4_=1K6+uOjf%mFhS`8X}a^uF4~;rA4}p_;1#_It&cl z+^Wc&Y}V???g2`~)l~HeHmXV4MF%MAYB7~f06;p%ZQrUK2!_ML=D`-Ww-UK)=p`;= zoni5E{xF}74eC!AfQ1{@pPJ2p_^eD?Y3jFPHPY!${dzzPe2eT@*M_OEJ0eHMMKE%o%{Ff zjQ}7Bli>*`d%%_fCT^LsYGNUW2z0}3+^yQOBYG9?w?IE^9o1h;HI^jk@Q($?W@XJz z%98moMD5qWQZeE-e#Qor zP;4{jwC!z0=e8m3lgCGm?|>dV{EHG36G|Yj$X^NKiO&OZRUFo|zTHr>Wg0ezU-g&K z!B~b;ulL~4x5EQd{HQB-PYE>NApycW327Z%)FV0)%D{)al}M%X^Y$~#Fv1|gwr0o? zT)G@W=);o+@ATTdFM*u|08Qe(XoOu%_+xxZW`Y7B;GCE^lCWdo?NHgMm_YGkjf?fm z>`3}F(}Ib}u{($y*H`EDP&_I;g|1{#4AXD#Tg-bG8T z@wd)%P1EVKmDsj>UlgX>MQUbtT4SD$1!8JRgAfZu938AyBbicgs2qAsG)4FSMt05b zA;l2nF@gbi7&)BjThUw!VHm(*ubY>^$Hq)mAoj#1=@aVi7 zcoPG+m~^5LrBx*k4lXj1I+~d|bHsJ`5t1+cqHT3qsvl4HNqs}r6nqSA4Vx@+|1&WO z*uYo7(`qSs;4^Np{gEFDv12I{ADj zy~*poJA#MLD~x*fbFm2wXgo^d<>h7RUQPmAcZWY{D}no}Lvi$n0jjg(G4Xa7G9*Z( zf4qv0{@lt^yYZmJe?hlLAK86nNoRv6UNI#JeZQ%RMU!#_DJ%x9Ve9S2E86F%2O{p& zl{O5%bynUwRRDSAp+sMuEhrhRoPw7fQ7dhlly|w;pb>}}%9;&;kMA=i9+lSDEIJqa zO*cs+pGr={_^qk%W?sp3x{x;95-KFLSNki~4DRHui=bS5yCsVbsrpZSWEi49!$<(6 znD7kR!p2I;f@Yc;kv0$S3W!Dd6RcHVTF^kxyF8ave1Wwfu2)e=;289U4S*D|WYxUd z)wGnfv$Yf>pq}naE569?=h*(%r4O%_<#-h@Y`OnO9Fv~Dv9Pefb&p~q5ik4X8zP7{ z>A5JgRFG{l)~{JXX1tBObu2iqn7PHQ2K5(2g%KGbH_>g;W5CWkBzLK2)%UO>wLcUI zzUHcvOgAQekijYYTOwu93&BeI-iRJ&9ZC9CXyuz`W9c1_N{SPkg}QvH*sqaE{reC|6ZzWH$3nZP9q-^^0-L1Ze$~b5 zK((tA-N;mfiAkw%CYp%8@Q)yXL2osH@ke=#FQEa)qO=w6>1t^~& zl2zXu8eaC!H^d*XAUoyCjT|)4N(uA#LIuoknO$#>k{epgU;eKGr33~%Sg+v^dX%7n zkZSHalKgxxTWjZ`o4aM0wpqamaS@~5Jc2)AR*l{k-&RAWYY{<9e~S@y4%MQXPS;)& zBd@m@g}ySYk+W(ePROJJSHtSJhNrZ{S-(as=X8AYiT%Eo;0jUTsMY%b)OFiqb&!J3 zeCF}qrC$7Xs1t1+wWD*9@mNVNe1`2(XeZr&+vg`p-x0G49r6kplllw$4=K+>7ZDK= z@93sP#q|?NkltwYi>GYvfmWwsb8VID?eIR`Bx~$zfsPV|&HnePJO~;HOdjn%{7;y=gamCkQX8)#&B2VeHl{O7BJ<$dEv7x-});rPGN zd#zuK|Ml~XThuq99^XBxd4tq`F`R;Ya`_9Jm%_Z;4R67RKU5%Dck9bcHkCA5n25BB=-RN~I3BZoA z;%g1;tztuY$MTX{6HRow=JcqgI02Nwi+2U#dof+~M>&uro{ocZu58c~={mo$y?vnK zsZP!oA;LTs49BN-hVOCAF!;uwwd?QY4JLam<^yK)`P-%ed#?FQe>iI0DG%r-x?&CV^*Ku5wp*mD)B!WhlX#Xq{o)u%Wp@frWRH$USQ){gZI4aH0%Tm zb)tm`hmQUd8uizV+&WAln2HDobnj;*KjL<-@;IP&weH;=s*w0Gtm{AI`2F{&(U=ja zuCML;a&WM5URX}>xLthi7iu0JTnGpVKy`})gM+`O#&KKWGMv200AVZ&E579n$H zbvgy=CEDm)1qVi@5Tea$LIWvbyps$99bV{uM87@ zS@vSZ&iv%Sk!1#v4hL^*a=+M0*Qjl_Mr{{o0js6qsHDgr9e8oAjF+?fZ?fQI-YMrg z3IDcf_5u_Z6rG;mqD5(;`tQ@bT&ykr7Iptk#1R$-)qGTM2@^8_$;^1Rk?4IMHp=m2CvrJ=u@oQ>&+zS!Ep2f2SU!%k0T9SRG*=)7XsCm?7 z-^W))`n^^!)(i~=^H)UW5#>EVL{))S7Y+Mg)L+A`^GlsMr;B$~QZDQ{78y~Gh(9NL zA8WjS9o=4%E7ppAKTeY?+8c=BWk2+k3_=!$Z=z!`*i2N_%Ccr32Sry>Hoi+Ig)Jnd zT+DhdR)n9twf1UKjU8|A?ImU=?j0VQ+S*plm}zQh-NM5uQ!RMoWHSx(dAWYZ9trY*rFfX^eC;ym~!URb>h)E>&rPBxhZ7r%8?GEo!?3LYQbsx9nDH z)y41K(MY|M8}L*yzL{l4?qua#okSJ;`H|88x(qy9wI*7?bzO;;id8Z|-6yKOW}B)o zmb^W$a9V_V2k|l#O`HFw4&jW)t!->8(8vjXdV;8~Gmk;5XV9HU08u@vFLJ_OuOBG9& zh=Txlc1<0F{EGEqkDe7hlZ9c}sPH{Dl?5CMa~bCx4z^Y2{_5mTqEmyU-S`E@heysu zbY%R_%a+5u{QUFtbFl8sO+GFzk1fY3k)Y76ob6-3pq*(3{-HGE3vVw%+`jk(;*FL% zli2@@8_8ykM36(%tSIduVQ%W9WLnQ6MT$XV**{<#X6=Y}y#)` zI|Fi$Y~pgNs^lq3%gZ4E{1h#x$BR*PYCxcppKPe#8KoUkiCT@ z`xqZ`j6;0SOm2`vSBHSWWDA0NyuEM%o*f4ak4QXiGl={U zXbteYxLZo=9piB1J-Vj9ucS=D=H=PMqN026Z6CR{o}LCQ;7{{HYHTID>3>TjP27= zqBXy-()>WpeMK_&1Lt5#&K=l!lWd!-^4Hlw*2$b>yK%uJ z{p#kiL^>@B^TJgo^f$Ez0oOsn^rfV}sdz&Yu97nTd?E{5GX&W%-r!zr)a2$u5pPv>?!*2*BdqXfLle*C5-6AXSKXUIBYA(U#O z_j`en8!x=C`PW0YZSKtj-^KKo&MI*Fi(XZBx>;ONJLKtz9>mPkJ3!~(f+9Zu`d17Q zbyg2e{Pub?pFVQ_*~G`iMUerqGGfBAWkRKZfCr`+GvBf5#y6U)kdJk0CCpFo??O9D z*r7VpS?lMJ^6rgme`M|Yw&d*}$j1EGlV*Q5kH3|j+aU7C#-iPACcuJRfZN==HGR5T z?#%{}L%TR_|M5`|iI^q?liUszs%d>ZLI~uia6cC7CP(+Txac=4X%LjGodha^e!)59Zq` zrc(u46|Tqs_!s}0cKFwM?dN^;&Ti_Ij@{2H%JQ!aYh$atVcnbt@NO z%1Zr-K3Tt(^vemjua0ys8LX!lFCd9}{yZi&j+^CRa?;w!sP{MP&78oHT?g3SS7#Vp_{wHEv1=JkyW{_z#FHRH|VsU!gZ#28?)#5`Ul3o zq`<98fl%b6wtwp-KELR>u|1aJ(7%f7UedR;9G`g4+t5^4pYQZtBBMY@I})s&sB2m{ zIt9BBelC%ZpW&0G+?DawtRxWgnpC$sbJz^|hN7_MgGApYlx?vt6X`s3`A1;#h=hc$ z8I$wV$?Ucry=-U*4D0zA5;x62mX%lNdtwV4nI7-IEZ#|!?CDmI0}OZ@U$}ozJT=q; z`#W2k49>J+LF@eUvzBO?I*1WH4b{Nk`DTOO*r~xqGxxvnmhkjL1D}MezTdAB}3bEU6<{*P&)wz_C*;On>{67B`0l( zsXQpaRlp=I^bkhM=O;295UpTapt{vUuYVka<$`JHS4@6vP%M3+BBntJW$>pUns`TW zQTK(+&l6EvDZbtN0pQimEOlSamL^C=GSqd|D8`{ec;OFWeM*oL2}4!RdJGL(4Ud)h zCKw@dNFrWZ^_(|KJ))zdM}u^FCRECK-MdUsRql4Z{>k;(&euh&yb2ysalKTTk&JgiZEe`F@0X8)w2!D9 zN!C(ZgA7$oX_Kmcy0H>`&aG<^+HiGcp$Rsq=|iJV1btyUncw$wCf1C z4F`8H9&(t^DjAIiG8c+8_%DZ`#e6yx$(QUz;Y|E8+GlvL!ZUw3y8vQ01_EOf?jO(1 z#-jmz)6P(J2~;?6B(pUBwAQ1_D0l5~3;e$EJJT_nlbWR+H_xS<8sF&dKK8$=e?DLo zNGVBXW@cRc{FIH2-rw7PXaw97?jDrh7`SBC@l$yTsq8&b%>(pht19k%u#ygV_KdD6 z^oTS1$+G|cg>bI%&pK?^ga7)h)Yf?EtI4M6m5T6TM5b+PzP)+<+li+$HN1uA}j!!=hw zCyO$C53o6BhUTtAgeawMMx~yZzm#*;9@4D6+1?LTB@(e287jRmUKYIAcQDJ3P2+)^ z4G(#iQjk5@5f)j)R|vFKo@U8dR#vE|lbVYK&VjBNe1%25cAOq->SXp^wA?bMZwi2G zNgjUm=A$Z16G%=@w$HR4Fu+Z?1rmn4g<43c176TqW|umStz)5k4~LZi?eJ;P=0qcq zW^*^xdMY8C>yA%t4U&&Sk-S<|c(d(Q%0|SQfSn99-OV^pb4D60nBz#s%1WK_zzS&x zukYi}QI|B4Kl)g}VJS1;XjI(LDAtMc95l{D5vM?v?8wXUhg-$W$VzgAw6gL{N76hTh@N=@V2>z^T%=de8n7%|&&-eKIs6s^EahxXe%adJ9~486;8w_LK^yxhD$y ztf_AVf1UIXEAR~ulK z!G$8!U z6kNW`k5sAsC36akP+a3}is>b>_*ElZLP9^G9A4hZEy5DZ?hxn#QOfJy8?y+Mew+|j zB_&hiot64O`HRvX{MM@qTnn0%zYSw%G#{U7`?f%h7*cn>z!okL898GufLfguk`t_Q zD%Sk7pSSq!zX!h5qJVUjwOv2^wOLnF1?QpSMM0ywgzRgx$8I$Ufpqtirmv4>YF$@T zdS4e-I1%29NjY-+>W>ZT&=&b(qylArae>PA-)IQGE z!L5=xGc<*o@91Lw*O=gNSQhp?dbs>CNfPIE2?NpE%u{(uPd;+!V_*W*`8U;Ek6gsI zTJ-Qkp(DTEqk}HyxPw3oHy@u<+K7aIC5=Yq$2 Date: Thu, 12 Jan 2023 17:16:17 +0200 Subject: [PATCH 003/168] Improve Device Profile Entity --- .../server/dao/model/ModelConstants.java | 3 + .../dao/model/sql/DeviceProfileEntity.java | 15 ++++ .../main/resources/sql/schema-entities.sql | 71 ++++++++++++++++--- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e24bde91fb..3736a236e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -185,6 +185,9 @@ public class ModelConstants { public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id"; + public static final String DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY = "certificate_hash"; + public static final String DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY = "certificate_value"; + public static final String DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY = "certificate_regex_pattern"; /** * Asset profile constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 893c9d549c..2aaaf61197 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -106,6 +106,15 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) private UUID externalId; + @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY) + private String certificateValue; + + @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY) + private String certificateHash; + + @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY) + private String certificateRegexPattern; + public DeviceProfileEntity() { super(); } @@ -123,6 +132,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.image = deviceProfile.getImage(); this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); + this.certificateHash = deviceProfile.getCertificateHash(); + this.certificateValue = deviceProfile.getCertificateValue(); + this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -182,6 +194,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId)); } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); + deviceProfile.setCertificateHash(certificateHash); + deviceProfile.setCertificateValue(certificateValue); + deviceProfile.setCertificateRegexPattern(certificateRegexPattern); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 73039274a6..c4c3317009 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -271,24 +271,75 @@ CREATE TABLE IF NOT EXISTS device_profile ( provision_type varchar(255), profile_data jsonb, description varchar, - search_text varchar(255), + search_text varchar +( + 255 +), is_default boolean, tenant_id uuid, firmware_id uuid, software_id uuid, default_rule_chain_id uuid, default_dashboard_id uuid, - default_queue_name varchar(255), + default_queue_name varchar +( + 255 +), provision_device_key varchar, + certificate_value varchar; + certificate_hash varchar, + certificate_regex_pattern varchar +( + 255 +), external_id uuid, - CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), - CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), - CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id), - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), - CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id), - CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES ota_package(id), - CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) -); + CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE +( + certificate_hash +), + CONSTRAINT device_profile_name_unq_key UNIQUE +( + tenant_id, + name +), + CONSTRAINT device_provision_key_unq_key UNIQUE +( + provision_device_key +), + CONSTRAINT device_profile_external_id_unq_key UNIQUE +( + tenant_id, + external_id +), + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY +( + default_rule_chain_id +) REFERENCES rule_chain +( + id +), + CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY +( + default_dashboard_id +) REFERENCES dashboard +( + id +), + CONSTRAINT fk_firmware_device_profile FOREIGN KEY +( + firmware_id +) REFERENCES ota_package +( + id +), + CONSTRAINT fk_software_device_profile FOREIGN KEY +( + software_id +) REFERENCES ota_package +( + id +) + ); DO $$ From 5d35fa1f261dfa2e421a786a184b87b04776f461 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 12 Jan 2023 17:18:29 +0200 Subject: [PATCH 004/168] Add ability to upgrade device credentials if chain match with uploaded device profile certificate. --- .../transport/DefaultTransportApiService.java | 62 +++++++++- common/cluster-api/src/main/proto/queue.proto | 65 ++++++---- .../dao/device/DeviceCredentialsService.java | 4 +- .../dao/device/DeviceProfileService.java | 2 + .../server/common/data/DeviceProfile.java | 9 +- .../mqtt/MqttSslHandlerProvider.java | 60 ++++++++- .../transport/mqtt/MqttTransportHandler.java | 115 +++++++++++++----- .../common/transport/TransportService.java | 9 ++ ...idateDeviceProfileCredentialsResponse.java | 28 +++++ .../service/DefaultTransportService.java | 29 ++++- .../server/common/transport/util/SslUtil.java | 17 +++ .../dao/device/DeviceCredentialsDao.java | 8 ++ .../device/DeviceCredentialsServiceImpl.java | 10 +- .../dao/device/DeviceProfileCacheKey.java | 14 ++- .../server/dao/device/DeviceProfileDao.java | 2 + .../dao/device/DeviceProfileServiceImpl.java | 10 ++ .../device/DeviceCredentialsRepository.java | 5 + .../sql/device/DeviceProfileRepository.java | 2 + .../sql/device/JpaDeviceCredentialsDao.java | 5 + .../dao/sql/device/JpaDeviceProfileDao.java | 5 + 20 files changed, 388 insertions(+), 73 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index f8b42ecc1e..9bc61d6493 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -65,8 +65,8 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; @@ -90,11 +90,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRespon import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -108,6 +112,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH; @@ -128,6 +134,7 @@ public class DefaultTransportApiService implements TransportApiService { private final TbTenantProfileCache tenantProfileCache; private final TbApiUsageStateService apiUsageStateService; private final DeviceService deviceService; + private final DeviceProfileService deviceProfileService; private final RelationService relationService; private final DeviceCredentialsService deviceCredentialsService; private final DbCallbackExecutorService dbCallbackExecutorService; @@ -159,6 +166,13 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); + } else if (transportApiRequestMsg.hasValidateProfileX509CertRequestMsg()) { + ValidateDeviceProfileX509CertRequestMsg msg = transportApiRequestMsg.getValidateProfileX509CertRequestMsg(); + result = validateDeviceProfileCertificate(msg.getHash()); + } else if (transportApiRequestMsg.hasUpdateOrCreateDeviceCertRequestMsg()) { + UpdateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getUpdateOrCreateDeviceCertRequestMsg(); + DeviceProfile deviceProfile = deviceProfileCache.find(new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB()))); + result = updateOrCreateDeviceCredentials(msg.getHash(), msg.getValue(), msg.getCommonName(), deviceProfile, DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -226,6 +240,31 @@ public class DefaultTransportApiService implements TransportApiService { } } + private ListenableFuture validateDeviceProfileCertificate(String credentialsId) { + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(credentialsId); + if (deviceProfile != null) { + return getDeviceProfileInfo(deviceProfile); + } + return getEmptyTransportApiResponseFuture(); + } + + private ListenableFuture updateOrCreateDeviceCredentials(String credentialsId, + String credentialsValue, + String deviceCN, + DeviceProfile deviceProfile, + DeviceCredentialsType credentialsType) { + String deviceName = extractRegex(deviceCN, deviceProfile.getCertificateRegexPattern()); + // find deviceCredentials by deviceName (device exists) + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByTenantIdAndDeviceName(deviceProfile.getTenantId(), deviceName); + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { + deviceCredentials.setCredentialsId(credentialsId); + deviceCredentials.setCredentialsValue(credentialsValue); + deviceCredentialsService.updateDeviceCredentials(deviceProfile.getTenantId(), deviceCredentials); + return getDeviceInfo(deviceCredentials); + } + return getEmptyTransportApiResponseFuture(); + } + private ListenableFuture validateUserNameCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); if (credentials != null) { @@ -507,6 +546,18 @@ public class DefaultTransportApiService implements TransportApiService { }, MoreExecutors.directExecutor()); } + private ListenableFuture getDeviceProfileInfo(DeviceProfile deviceProfile) { + ValidateDeviceProfileCredentialsResponseMsg.Builder builder = ValidateDeviceProfileCredentialsResponseMsg.newBuilder() + .setDeviceProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) + .setIsDeviceProfileFound(true); + + return Futures.immediateFuture( + TransportApiResponseMsg.newBuilder() + .setValidateDeviceProfileResponseMsg(builder.build()) + .build()); + } + private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) @@ -664,4 +715,13 @@ public class DefaultTransportApiService implements TransportApiService { private Long checkLong(Long l) { return l != null ? l : 0; } + + private String extractRegex(String commonName, String regex) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(commonName); + if (matcher.find()) { + return matcher.group(0); + } + return commonName; + } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 5a28d42546..405b96726a 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -169,6 +169,18 @@ message ValidateDeviceX509CertRequestMsg { string hash = 1; } +message ValidateDeviceProfileX509CertRequestMsg { + string hash = 1; +} + +message UpdateOrCreateDeviceX509CertRequestMsg { + string hash = 1; + string value = 2; + string commonName = 3; + int64 deviceProfileIdMSB = 4; + int64 deviceProfileIdLSB = 5; +} + message ValidateBasicMqttCredRequestMsg { string clientId = 1; string userName = 2; @@ -181,6 +193,12 @@ message ValidateDeviceCredentialsResponseMsg { bytes profileBody = 3; } +message ValidateDeviceProfileCredentialsResponseMsg { + int64 deviceProfileIdMSB = 1; + int64 deviceProfileIdLSB = 2; + bool isDeviceProfileFound = 3; +} + message GetOrCreateDeviceFromGatewayRequestMsg { int64 gatewayIdMSB = 1; int64 gatewayIdLSB = 2; @@ -885,33 +903,36 @@ message VersionControlResponseMsg { message TransportApiRequestMsg { ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; - GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; - GetEntityProfileRequestMsg entityProfileRequestMsg = 4; - LwM2MRequestMsg lwM2MRequestMsg = 5; - ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; - ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; - ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; - GetResourceRequestMsg resourceRequestMsg = 9; - GetOtaPackageRequestMsg otaPackageRequestMsg = 10; - GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11; - GetDeviceRequestMsg deviceRequestMsg = 12; - GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; - GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; + ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 3; + GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 4; + GetEntityProfileRequestMsg entityProfileRequestMsg = 5; + LwM2MRequestMsg lwM2MRequestMsg = 6; + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 7; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 8; + ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 9; + GetResourceRequestMsg resourceRequestMsg = 10; + GetOtaPackageRequestMsg otaPackageRequestMsg = 11; + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 12; + GetDeviceRequestMsg deviceRequestMsg = 13; + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 14; + GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 15; + UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 16; } /* Response from ThingsBoard Core Service to Transport Service */ message TransportApiResponseMsg { ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; - GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; - GetEntityProfileResponseMsg entityProfileResponseMsg = 3; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; - GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5; - LwM2MResponseMsg lwM2MResponseMsg = 6; - GetResourceResponseMsg resourceResponseMsg = 7; - GetOtaPackageResponseMsg otaPackageResponseMsg = 8; - GetDeviceResponseMsg deviceResponseMsg = 9; - GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; - repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; + ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 2; + GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 3; + GetEntityProfileResponseMsg entityProfileResponseMsg = 4; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 5; + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 6; + LwM2MResponseMsg lwM2MResponseMsg = 7; + GetResourceResponseMsg resourceResponseMsg = 8; + GetOtaPackageResponseMsg otaPackageResponseMsg = 9; + GetDeviceResponseMsg deviceResponseMsg = 10; + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 11; + repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 12; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java index 42d6d10a1a..150106e3cb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; -import com.fasterxml.jackson.databind.JsonNode; public interface DeviceCredentialsService { @@ -26,6 +26,8 @@ public interface DeviceCredentialsService { DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId); + DeviceCredentials findDeviceCredentialsByTenantIdAndDeviceName(TenantId tenantId, String deviceName); + DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java index 2e65e159cd..6ead3e08c5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java @@ -39,6 +39,8 @@ public interface DeviceProfileService extends EntityDaoService { PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); + DeviceProfile findDeviceProfileByCertificateHash(String credentialsId); + DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); DeviceProfile createDefaultDeviceProfile(TenantId tenantId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index a66f3a40d9..2a5b11f1ff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -16,7 +16,6 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -28,7 +27,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; @@ -68,6 +66,13 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; + @ApiModelProperty(position = 16, value = "CA certificate value") + private String certificateValue; + @ApiModelProperty(position = 17, value = "CA certificate hash") + private String certificateHash; + @ApiModelProperty(position = 18, value = "Regex for fetch deviceName from CN") + private String certificateRegexPattern; + @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the root rule chain will be used to process those messages.") diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 98aaca7f06..ed2326946f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -24,12 +24,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; import org.thingsboard.server.common.transport.util.SslUtil; @@ -142,8 +143,15 @@ public class MqttSslHandlerProvider { } @Override - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + String deviceCN = SslUtil.parseCommonName(chain[0]); + String deviceCert; + String deviceCredentialsValue = SslUtil.getCertificateString(chain[0]); + try { + deviceCert = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); + } String credentialsBody = null; for (X509Certificate cert : chain) { try { @@ -152,13 +160,53 @@ public class MqttSslHandlerProvider { final String[] credentialsBodyHolder = new String[1]; CountDownLatch latch = new CountDownLatch(1); transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback() { + new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { if (!StringUtils.isEmpty(msg.getCredentials())) { credentialsBodyHolder[0] = msg.getCredentials(); + latch.countDown(); + } else { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { + if (msg.isDeviceProfileFound()) { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() + .setHash(deviceCert) + .setCommonName(deviceCN) + .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) + .build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + System.out.println("msg.getCredentials() = " + msg.getCredentials()); + credentialsBodyHolder[0] = msg.getCredentials(); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + } + ); + } else { + latch.countDown(); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); } - latch.countDown(); } @Override @@ -168,7 +216,7 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (strCert.equals(credentialsBodyHolder[0])) { + if (deviceCredentialsValue.equals(credentialsBodyHolder[0])) { credentialsBody = credentialsBodyHolder[0]; break; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 7515a1fcd2..5044d5a35e 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -21,7 +21,6 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.mqtt.MqttConnAckMessage; -import io.netty.handler.codec.mqtt.MqttConnAckVariableHeader; import io.netty.handler.codec.mqtt.MqttConnectMessage; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import io.netty.handler.codec.mqtt.MqttFixedHeader; @@ -63,12 +62,12 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; @@ -80,7 +79,7 @@ import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; import java.net.InetSocketAddress; -import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -90,16 +89,15 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.amazonaws.util.StringUtils.UTF8; -import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT; import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; -import static io.netty.handler.codec.mqtt.MqttMessageType.UNSUBACK; import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE; import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; @@ -810,9 +808,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg)); } else { - X509Certificate cert; - if (sslHandler != null && (cert = getX509Certificate()) != null) { - processX509CertConnect(ctx, cert, msg); + X509Certificate[] chain; + if (sslHandler != null && (chain = getX509Certificate()) != null) { + processX509CertConnect(ctx, chain, msg); } else { processAuthTokenConnect(ctx, msg); } @@ -848,27 +846,82 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }); } - private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) { + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { try { - if (!context.isSkipValidityCheckForClientCert()) { - cert.checkValidity(); + String deviceCN = SslUtil.parseCommonName(chain[0]); + String deviceCertHash = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); + for (X509Certificate cert : chain) { + try { + String strCert = SslUtil.getCertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + final ValidateDeviceCredentialsResponse[] validateDeviceCredentialsResponses = new ValidateDeviceCredentialsResponse[1]; + CountDownLatch latch = new CountDownLatch(1); + transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + validateDeviceCredentialsResponses[0] = msg; + latch.countDown(); + } else { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { + if (msg.isDeviceProfileFound()) { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() + .setHash(deviceCertHash) + .setCommonName(deviceCN) + .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) + .build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + validateDeviceCredentialsResponses[0] = msg; + latch.countDown(); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + } + ); + } else { + latch.countDown(); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + if (validateDeviceCredentialsResponses[0] != null && validateDeviceCredentialsResponses[0].hasDeviceInfo()) { + onValidateDeviceResponse(validateDeviceCredentialsResponses[0], ctx, connectMessage); + break; + } + } catch (InterruptedException | CertificateEncodingException e) { + log.error(e.getMessage(), e); + } } - String strCert = SslUtil.getCertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - onValidateDeviceResponse(msg, ctx, connectMessage); - } - - @Override - public void onError(Throwable e) { - log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); - ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); - ctx.close(); - } - }); } catch (Exception e) { context.onAuthFailure(address); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage)); @@ -877,17 +930,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private X509Certificate getX509Certificate() { + private X509Certificate[] getX509Certificate() { try { - Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); - if (certChain.length > 0) { - return (X509Certificate) certChain[0]; - } + return (X509Certificate[]) sslHandler.engine().getSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); return null; } - return null; } private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index f1d3f7e26c..21e668cf37 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -52,8 +53,10 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; @@ -87,6 +90,12 @@ public interface TransportService { void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback); + void process(DeviceTransportType transportType, ValidateDeviceProfileX509CertRequestMsg msg, + TransportServiceCallback callback); + + void process(DeviceTransportType transportType, UpdateOrCreateDeviceX509CertRequestMsg msg, + TransportServiceCallback callback); + void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java new file mode 100644 index 0000000000..cfbe79bf96 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.auth; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceProfileId; + +@Data +@Builder +public class ValidateDeviceProfileCredentialsResponse { + + private final DeviceProfileId deviceProfileId; + private final boolean isDeviceProfileFound; +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index bf03122b18..a41c1d6055 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -71,8 +71,8 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -97,6 +97,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.AfterStartUp; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -427,6 +428,32 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } + @Override + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceProfileX509CertRequestMsg requestMsg, + TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), + TransportApiRequestMsg.newBuilder().setValidateProfileX509CertRequestMsg(requestMsg).build()); + ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { + TransportProtos.ValidateDeviceProfileCredentialsResponseMsg msg = tmp.getValue().getValidateDeviceProfileResponseMsg(); + ValidateDeviceProfileCredentialsResponse.ValidateDeviceProfileCredentialsResponseBuilder result = ValidateDeviceProfileCredentialsResponse.builder(); + DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB())); + result.deviceProfileId(deviceProfileId); + result.isDeviceProfileFound(msg.getIsDeviceProfileFound()); + return result.build(); + }, MoreExecutors.directExecutor()); + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); + } + + @Override + public void process(DeviceTransportType transportType, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg requestMsg, + TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder() + .setUpdateOrCreateDeviceCertRequestMsg(requestMsg).build()); + doProcess(transportType, protoMsg, callback); + } + @Override public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index 45a09a9899..51ed2cd948 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -16,11 +16,17 @@ package org.thingsboard.server.common.transport.util; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.springframework.util.Base64Utils; import org.thingsboard.server.common.msg.EncryptionUtil; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; /** * @author Valerii Sosliuk @@ -35,4 +41,15 @@ public class SslUtil { throws CertificateEncodingException { return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); } + + public static String parseCommonName(X509Certificate certificate) { + X500Name x500name; + try { + x500name = new JcaX509CertificateHolder(certificate).getSubject(); + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); + } + RDN cn = x500name.getRDNs(BCStyle.CN)[0]; + return IETFUtils.valueToString(cn.getFirst().getValue()); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java index 10363ac5d7..b6758af59c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java @@ -53,4 +53,12 @@ public interface DeviceCredentialsDao extends Dao { */ DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId); + /** + * Find device credentials by device name. + * + * @param deviceName the device name + * @return the device credentials object + */ + DeviceCredentials findByTenantIdAndDeviceName(TenantId tenantId, String deviceName); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index c4cbabfb3d..c21031fe65 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -16,15 +16,12 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; @@ -86,6 +83,13 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService, ExportableEntityDa DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findByName(TenantId tenantId, String profileName); + + DeviceProfile findByCertificateHash(String certificateHash); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 9dad6a22d9..41a51176f4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -53,6 +53,7 @@ import java.util.Map; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateString; @Service("DeviceProfileDaoService") @Slf4j @@ -61,6 +62,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByCertificateHash(certificateHash), true); + } + @Override public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String name) { log.trace("Executing findOrCreateDefaultDeviceProfile"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java index 0dd35a39b8..7642b45d5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.sql.device; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.DeviceCredentialsEntity; import java.util.UUID; @@ -28,4 +30,7 @@ public interface DeviceCredentialsRepository extends JpaRepository Date: Fri, 13 Jan 2023 17:27:08 +0200 Subject: [PATCH 005/168] Fix sql formatting and incorrect proto-message updating --- common/cluster-api/src/main/proto/queue.proto | 50 ++++++------ .../mqtt/MqttSslHandlerProvider.java | 15 ++-- .../transport/mqtt/MqttTransportHandler.java | 6 +- .../sql/device/JpaDeviceCredentialsDao.java | 2 +- .../main/resources/sql/schema-entities.sql | 76 +++++-------------- 5 files changed, 52 insertions(+), 97 deletions(-) diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 405b96726a..ace1f3cf6d 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -903,36 +903,36 @@ message VersionControlResponseMsg { message TransportApiRequestMsg { ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; - ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 3; - GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 4; - GetEntityProfileRequestMsg entityProfileRequestMsg = 5; - LwM2MRequestMsg lwM2MRequestMsg = 6; - ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 7; - ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 8; - ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 9; - GetResourceRequestMsg resourceRequestMsg = 10; - GetOtaPackageRequestMsg otaPackageRequestMsg = 11; - GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 12; - GetDeviceRequestMsg deviceRequestMsg = 13; - GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 14; - GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 15; - UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 16; + GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; + GetEntityProfileRequestMsg entityProfileRequestMsg = 4; + LwM2MRequestMsg lwM2MRequestMsg = 5; + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; + ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; + GetResourceRequestMsg resourceRequestMsg = 9; + GetOtaPackageRequestMsg otaPackageRequestMsg = 10; + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11; + GetDeviceRequestMsg deviceRequestMsg = 12; + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; + GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; + UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 15; + ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 16; } /* Response from ThingsBoard Core Service to Transport Service */ message TransportApiResponseMsg { ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; - ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 2; - GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 3; - GetEntityProfileResponseMsg entityProfileResponseMsg = 4; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 5; - GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 6; - LwM2MResponseMsg lwM2MResponseMsg = 7; - GetResourceResponseMsg resourceResponseMsg = 8; - GetOtaPackageResponseMsg otaPackageResponseMsg = 9; - GetDeviceResponseMsg deviceResponseMsg = 10; - GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 11; - repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 12; + GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; + GetEntityProfileResponseMsg entityProfileResponseMsg = 3; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5; + LwM2MResponseMsg lwM2MResponseMsg = 6; + GetResourceResponseMsg resourceResponseMsg = 7; + GetOtaPackageResponseMsg otaPackageResponseMsg = 8; + GetDeviceResponseMsg deviceResponseMsg = 9; + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; + repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; + ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 12; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index ed2326946f..237ff86375 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -145,13 +145,8 @@ public class MqttSslHandlerProvider { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { String deviceCN = SslUtil.parseCommonName(chain[0]); - String deviceCert; - String deviceCredentialsValue = SslUtil.getCertificateString(chain[0]); - try { - deviceCert = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); - } catch (CertificateEncodingException e) { - throw new RuntimeException(e); - } + String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); + String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); String credentialsBody = null; for (X509Certificate cert : chain) { try { @@ -175,7 +170,8 @@ public class MqttSslHandlerProvider { if (msg.isDeviceProfileFound()) { transportService.process(DeviceTransportType.MQTT, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(deviceCert) + .setHash(clientDeviceCertHash) + .setValue(clientDeviceCertValue) .setCommonName(deviceCN) .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) @@ -183,7 +179,6 @@ public class MqttSslHandlerProvider { new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { - System.out.println("msg.getCredentials() = " + msg.getCredentials()); credentialsBodyHolder[0] = msg.getCredentials(); latch.countDown(); } @@ -216,7 +211,7 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (deviceCredentialsValue.equals(credentialsBodyHolder[0])) { + if (clientDeviceCertValue.equals(credentialsBodyHolder[0])) { credentialsBody = credentialsBodyHolder[0]; break; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 5044d5a35e..803a8ff3ca 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -849,7 +849,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { try { String deviceCN = SslUtil.parseCommonName(chain[0]); - String deviceCertHash = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); + String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); + String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); for (X509Certificate cert : chain) { try { String strCert = SslUtil.getCertificateString(cert); @@ -872,7 +873,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (msg.isDeviceProfileFound()) { transportService.process(DeviceTransportType.MQTT, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(deviceCertHash) + .setHash(clientDeviceCertHash) + .setValue(clientDeviceCertValue) .setCommonName(deviceCN) .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java index a56a13c98a..01a746c9b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java @@ -69,6 +69,6 @@ public class JpaDeviceCredentialsDao extends JpaAbstractDao Date: Thu, 19 Jan 2023 15:18:15 +0200 Subject: [PATCH 006/168] Add device profile upgrade --- .../src/main/data/upgrade/3.4.3/schema_update.sql | 12 ++++++++++++ dao/src/main/resources/sql/schema-entities.sql | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index e6fd32bc2d..87e9e98aef 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -24,3 +24,15 @@ CREATE TABLE IF NOT EXISTS alarm_comment ( CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE ) PARTITION BY RANGE (created_time); CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); + + + +-- DEVICE PROFILE CERTIFICATE START + +ALTER TABLE device_profile + ADD COLUMN IF NOT EXISTS certificate_hash varchar, + ADD COLUMN IF NOT EXISTS certificate_value varchar, + ADD COLUMN IF NOT EXISTS certificate_regex_pattern varchar(255), + DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, + ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); +-- DEVICE PROFILE CERTIFICATE END diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 5d2921370a..6a2996bff7 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -292,7 +292,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( default_dashboard_id uuid, default_queue_name varchar(255), provision_device_key varchar, - certificate_value varchar; + certificate_value varchar, certificate_hash varchar, certificate_regex_pattern varchar(255), default_edge_rule_chain_id uuid, From 18c1440a488dcc6594d2a5dbb5457ec0e37e32de Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 19 Jan 2023 15:21:48 +0200 Subject: [PATCH 007/168] Add validation on device profile create or update --- .../server/common/data/DeviceProfile.java | 2 +- .../mqtt/MqttSslHandlerProvider.java | 4 +- .../transport/mqtt/MqttTransportHandler.java | 111 +++++------------- .../dao/device/DeviceProfileServiceImpl.java | 24 +++- .../validator/DeviceProfileDataValidator.java | 54 +++++++++ 5 files changed, 107 insertions(+), 88 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index d0efc8cb5b..54989e1aab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -70,7 +70,7 @@ public class DeviceProfile extends SearchTextBased implements H private String certificateValue; @ApiModelProperty(position = 17, value = "CA certificate hash") private String certificateHash; - @ApiModelProperty(position = 18, value = "Regex for fetch deviceName from CN") + @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN") private String certificateRegexPattern; @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 237ff86375..2b4a24e116 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -124,7 +124,7 @@ public class MqttSslHandlerProvider { static class ThingsboardMqttX509TrustManager implements X509TrustManager { private final X509TrustManager trustManager; - private TransportService transportService; + private final TransportService transportService; ThingsboardMqttX509TrustManager(X509TrustManager trustManager, TransportService transportService) { this.trustManager = trustManager; @@ -220,7 +220,7 @@ public class MqttSslHandlerProvider { } } if (credentialsBody == null) { - throw new CertificateException("Invalid Device Certificate"); + throw new CertificateException("Invalid Certificate's chain"); } } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 803a8ff3ca..9159759c28 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -62,7 +62,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.util.SslUtil; @@ -79,7 +78,7 @@ import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; import java.net.InetSocketAddress; -import java.security.cert.CertificateEncodingException; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -89,7 +88,6 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -808,9 +806,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg)); } else { - X509Certificate[] chain; - if (sslHandler != null && (chain = getX509Certificate()) != null) { - processX509CertConnect(ctx, chain, msg); + X509Certificate cert; + if (sslHandler != null && (cert = getX509Certificate()) != null) { + processX509CertConnect(ctx, cert, msg); } else { processAuthTokenConnect(ctx, msg); } @@ -846,84 +844,25 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }); } - private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) { try { - String deviceCN = SslUtil.parseCommonName(chain[0]); - String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); - String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); - for (X509Certificate cert : chain) { - try { - String strCert = SslUtil.getCertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - final ValidateDeviceCredentialsResponse[] validateDeviceCredentialsResponses = new ValidateDeviceCredentialsResponse[1]; - CountDownLatch latch = new CountDownLatch(1); - transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - validateDeviceCredentialsResponses[0] = msg; - latch.countDown(); - } else { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { - if (msg.isDeviceProfileFound()) { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(clientDeviceCertHash) - .setValue(clientDeviceCertValue) - .setCommonName(deviceCN) - .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) - .build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - validateDeviceCredentialsResponses[0] = msg; - latch.countDown(); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - } - ); - } else { - latch.countDown(); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - latch.await(10, TimeUnit.SECONDS); - if (validateDeviceCredentialsResponses[0] != null && validateDeviceCredentialsResponses[0].hasDeviceInfo()) { - onValidateDeviceResponse(validateDeviceCredentialsResponses[0], ctx, connectMessage); - break; - } - } catch (InterruptedException | CertificateEncodingException e) { - log.error(e.getMessage(), e); - } + if (!context.isSkipValidityCheckForClientCert()) { + cert.checkValidity(); } + String strCert = SslUtil.getCertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + onValidateDeviceResponse(msg, ctx, connectMessage); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + } + }); } catch (Exception e) { context.onAuthFailure(address); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage)); @@ -932,13 +871,17 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private X509Certificate[] getX509Certificate() { + private X509Certificate getX509Certificate() { try { - return (X509Certificate[]) sslHandler.engine().getSession().getPeerCertificates(); + Certificate[] certChains = sslHandler.engine().getSession().getPeerCertificates(); + if (certChains.length > 1) { + return (X509Certificate) certChains[0]; + } } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); return null; } + return null; } private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 41a51176f4..8cd08739b3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.queue.QueueService; @@ -51,6 +52,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -62,7 +65,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService rootCa = new HashSet<>(); + try { + String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar); + FileInputStream is = new FileInputStream(filename); + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + String password = "changeit"; + keystore.load(is, password.toCharArray()); + + PKIXParameters params = new PKIXParameters(keystore); + for (TrustAnchor ta : params.getTrustAnchors()) { + X509Certificate cert = ta.getTrustedCert(); + rootCa.add(EncryptionUtil.getSha3Hash(getCertificateString(cert))); + } + } catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | + InvalidAlgorithmParameterException | IOException ignored) { + } + return rootCa.contains(deviceProfileHash); + } + + private String getCertificateString(Certificate cert) throws CertificateEncodingException { + return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); + } } From b4a9ba26e7b77f09bd367a54f34bdaef2bcc7676 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 20 Jan 2023 19:00:19 +0200 Subject: [PATCH 008/168] Add new field: update database and upgrade script --- application/src/main/data/upgrade/3.4.3/schema_update.sql | 2 ++ dao/src/main/resources/sql/schema-entities.sql | 1 + 2 files changed, 3 insertions(+) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 87e9e98aef..78203951ec 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -33,6 +33,8 @@ ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS certificate_hash varchar, ADD COLUMN IF NOT EXISTS certificate_value varchar, ADD COLUMN IF NOT EXISTS certificate_regex_pattern varchar(255), + ADD COLUMN IF NOT EXISTS allow_create_device_by_x509 boolean, DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); + -- DEVICE PROFILE CERTIFICATE END diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6a2996bff7..eed6f237b6 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -297,6 +297,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( certificate_regex_pattern varchar(255), default_edge_rule_chain_id uuid, external_id uuid, + allow_create_device_by_x509 boolean, CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash), CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), From 7957da4a9e2ab7922074848018e8dcde8e2bab23 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 20 Jan 2023 19:02:14 +0200 Subject: [PATCH 009/168] Updated: send full chain to kafka with single message and do whole logic on core --- .../transport/DefaultTransportApiService.java | 148 +++++++++++++----- common/cluster-api/src/main/proto/queue.proto | 22 +-- .../dao/device/DeviceCredentialsService.java | 2 - .../server/common/data/DeviceProfile.java | 13 +- .../data/DeviceProfileProvisionType.java | 3 +- ...509CertificateProvisionConfiguration.java} | 18 ++- .../DeviceProfileProvisionConfiguration.java | 3 +- .../server/common/msg/EncryptionUtil.java | 5 + .../mqtt/MqttSslHandlerProvider.java | 104 ++++-------- .../transport/mqtt/MqttTransportHandler.java | 13 +- .../common/transport/TransportService.java | 11 +- .../service/DefaultTransportService.java | 30 +--- .../server/common/transport/util/SslUtil.java | 12 ++ .../dao/device/DeviceCredentialsDao.java | 8 - .../device/DeviceCredentialsServiceImpl.java | 7 - .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/DeviceProfileEntity.java | 5 + .../validator/DeviceProfileDataValidator.java | 20 +-- .../device/DeviceCredentialsRepository.java | 5 - .../sql/device/JpaDeviceCredentialsDao.java | 5 - 20 files changed, 211 insertions(+), 224 deletions(-) rename common/{transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java => data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java} (58%) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 9bc61d6493..0bf0ff8035 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.ota.OtaPackageDataCache; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; @@ -65,6 +67,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; @@ -90,11 +93,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRespon import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileCredentialsResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -105,6 +105,13 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -166,13 +173,9 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); - } else if (transportApiRequestMsg.hasValidateProfileX509CertRequestMsg()) { - ValidateDeviceProfileX509CertRequestMsg msg = transportApiRequestMsg.getValidateProfileX509CertRequestMsg(); - result = validateDeviceProfileCertificate(msg.getHash()); - } else if (transportApiRequestMsg.hasUpdateOrCreateDeviceCertRequestMsg()) { - UpdateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getUpdateOrCreateDeviceCertRequestMsg(); - DeviceProfile deviceProfile = deviceProfileCache.find(new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB()))); - result = updateOrCreateDeviceCredentials(msg.getHash(), msg.getValue(), msg.getCommonName(), deviceProfile, DeviceCredentialsType.X509_CERTIFICATE); + } else if (transportApiRequestMsg.hasValidateOrCreateX509CertRequestMsg()) { + TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateOrCreateX509CertRequestMsg(); + result = validateOrCreateDeviceX509Certificate(msg.getCertificate(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -240,27 +243,43 @@ public class DefaultTransportApiService implements TransportApiService { } } - private ListenableFuture validateDeviceProfileCertificate(String credentialsId) { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(credentialsId); - if (deviceProfile != null) { - return getDeviceProfileInfo(deviceProfile); - } - return getEmptyTransportApiResponseFuture(); - } + private ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + try { + List chain = getX509CertificateChainFromString(certChain); + String updateDeviceCertificateValue = SslUtil.getCertificateString(chain.get(0)); + String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); + + for (X509Certificate cert: chain) { + String certificateValue = SslUtil.getCertificateString(cert); + String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); + if (credentials != null && credentials.getCredentialsType() == credentialsType) { + return getDeviceInfo(credentials); + } + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); + if (deviceProfile != null) { + String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); + String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern()); + Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); + if (device != null) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { + deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + } else if (deviceCredentials == null) { + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + } + return getDeviceInfo(deviceCredentials); + } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { + Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); + deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + return getDeviceInfo(deviceCredentials); + } - private ListenableFuture updateOrCreateDeviceCredentials(String credentialsId, - String credentialsValue, - String deviceCN, - DeviceProfile deviceProfile, - DeviceCredentialsType credentialsType) { - String deviceName = extractRegex(deviceCN, deviceProfile.getCertificateRegexPattern()); - // find deviceCredentials by deviceName (device exists) - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByTenantIdAndDeviceName(deviceProfile.getTenantId(), deviceName); - if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { - deviceCredentials.setCredentialsId(credentialsId); - deviceCredentials.setCredentialsValue(credentialsValue); - deviceCredentialsService.updateDeviceCredentials(deviceProfile.getTenantId(), deviceCredentials); - return getDeviceInfo(deviceCredentials); + } + } + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); } return getEmptyTransportApiResponseFuture(); } @@ -546,18 +565,6 @@ public class DefaultTransportApiService implements TransportApiService { }, MoreExecutors.directExecutor()); } - private ListenableFuture getDeviceProfileInfo(DeviceProfile deviceProfile) { - ValidateDeviceProfileCredentialsResponseMsg.Builder builder = ValidateDeviceProfileCredentialsResponseMsg.newBuilder() - .setDeviceProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) - .setIsDeviceProfileFound(true); - - return Futures.immediateFuture( - TransportApiResponseMsg.newBuilder() - .setValidateDeviceProfileResponseMsg(builder.build()) - .build()); - } - private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) @@ -716,7 +723,7 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } - private String extractRegex(String commonName, String regex) { + private String extractDeviceNameFromCNByRegEx(String commonName, String regex) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commonName); if (matcher.find()) { @@ -724,4 +731,59 @@ public class DefaultTransportApiService implements TransportApiService { } return commonName; } + + private List getX509CertificateChainFromString(String certificateChain) { + List chain = new ArrayList<>(); + String[] test = Arrays.stream(certificateChain.split("-----BEGIN CERTIFICATE-----")).filter(e -> e.trim().length() > 0).map(EncryptionUtil::certTrimNewLines).toArray(String[]::new); + Arrays.stream(test).forEach(s -> { + try { + chain.add(readCertFile(s)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return chain; + } + + private X509Certificate readCertFile(String fileContent) throws Exception { + X509Certificate certificate = null; + if (fileContent != null && !fileContent.trim().isEmpty()) { + fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(fileContent); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + try (InputStream inStream = new ByteArrayInputStream(decoded)) { + certificate = (X509Certificate) certFactory.generateCertificate(inStream); + } + } + return certificate; + } + + private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, + String certificateHash, DeviceCredentialsType credentialsType) { + deviceCredentials.setCredentialsId(certificateHash); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsType(credentialsType); + return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); + } + + private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, String certificateValue, + String certificateHash, DeviceCredentialsType credentialsType) { + DeviceCredentials createDevCredentials = new DeviceCredentials(); + createDevCredentials.setDeviceId(deviceId); + createDevCredentials.setCredentialsType(credentialsType); + createDevCredentials.setCredentialsId(certificateHash); + createDevCredentials.setCredentialsValue(certificateValue); + return deviceCredentialsService.createDeviceCredentials(tenantId, createDevCredentials); + } + + private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId, String deviceName, String type) { + Device device = new Device(); + device.setTenantId(tenantId); + device.setDeviceProfileId(deviceProfileId); + device.setName(deviceName); + device.setType(type); + return deviceService.saveDevice(device); + } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index ace1f3cf6d..b9741429f7 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -169,16 +169,8 @@ message ValidateDeviceX509CertRequestMsg { string hash = 1; } -message ValidateDeviceProfileX509CertRequestMsg { - string hash = 1; -} - -message UpdateOrCreateDeviceX509CertRequestMsg { - string hash = 1; - string value = 2; - string commonName = 3; - int64 deviceProfileIdMSB = 4; - int64 deviceProfileIdLSB = 5; +message ValidateOrCreateDeviceX509CertRequestMsg { + string certificate = 1; } message ValidateBasicMqttCredRequestMsg { @@ -193,12 +185,6 @@ message ValidateDeviceCredentialsResponseMsg { bytes profileBody = 3; } -message ValidateDeviceProfileCredentialsResponseMsg { - int64 deviceProfileIdMSB = 1; - int64 deviceProfileIdLSB = 2; - bool isDeviceProfileFound = 3; -} - message GetOrCreateDeviceFromGatewayRequestMsg { int64 gatewayIdMSB = 1; int64 gatewayIdLSB = 2; @@ -915,8 +901,7 @@ message TransportApiRequestMsg { GetDeviceRequestMsg deviceRequestMsg = 12; GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; - UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 15; - ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 16; + ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateX509CertRequestMsg = 15; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -932,7 +917,6 @@ message TransportApiResponseMsg { GetDeviceResponseMsg deviceResponseMsg = 9; GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; - ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 12; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java index 150106e3cb..1c0f2578ed 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java @@ -26,8 +26,6 @@ public interface DeviceCredentialsService { DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId); - DeviceCredentials findDeviceCredentialsByTenantIdAndDeviceName(TenantId tenantId, String deviceName); - DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 54989e1aab..b9b1053d99 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,12 +66,15 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 16, value = "CA certificate value") + @ApiModelProperty(position = 16, value = "CA certificate value. ") private String certificateValue; - @ApiModelProperty(position = 17, value = "CA certificate hash") + @ApiModelProperty(position = 17, value = "CA certificate hash. ") private String certificateHash; - @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN") + @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN. ") private String certificateRegexPattern; + @ApiModelProperty(position = 19, value = "Allow to create new devices by x509 provision strategy. ") + private boolean allowCreateNewDevicesByX509Strategy; + @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + @@ -128,6 +131,10 @@ public class DeviceProfile extends SearchTextBased implements H this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); + this.allowCreateNewDevicesByX509Strategy = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); + this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); + this.certificateValue = deviceProfile.getCertificateValue(); + this.certificateHash = deviceProfile.getCertificateHash(); this.externalId = deviceProfile.getExternalId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java index 319ebadcbc..7ee03d7227 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java @@ -18,5 +18,6 @@ package org.thingsboard.server.common.data; public enum DeviceProfileProvisionType { DISABLED, ALLOW_CREATE_NEW_DEVICES, - CHECK_PRE_PROVISIONED_DEVICES + CHECK_PRE_PROVISIONED_DEVICES, + ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java similarity index 58% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java index cfbe79bf96..e84d09829f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java @@ -13,16 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.auth; -import lombok.Builder; +package org.thingsboard.server.common.data.device.profile; + import lombok.Data; -import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; @Data -@Builder -public class ValidateDeviceProfileCredentialsResponse { +public class AllowCreatingNewDevicesByX509CertificateProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE; + } - private final DeviceProfileId deviceProfileId; - private final boolean isDeviceProfileFound; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java index b29641fe6f..7a70c00b98 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -31,7 +31,8 @@ import java.io.Serializable; @JsonSubTypes({ @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"), @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"), - @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")}) + @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES"), + @JsonSubTypes.Type(value = AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.class, name = "ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE")}) public interface DeviceProfileProvisionConfiguration extends Serializable { String getProvisionDeviceSecret(); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index c57c3a885b..6bccac1591 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -35,6 +35,11 @@ public class EncryptionUtil { .replaceAll("-----END CERTIFICATE-----", ""); } + public static String certTrimNewLinesWithoutBeginEnd(String input) { + return input.replaceAll("\n", "") + .replaceAll("\r", ""); + } + public static String pubkTrimNewLines(String input) { return input.replaceAll("-----BEGIN PUBLIC KEY-----", "") .replaceAll("\n", "") diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 2b4a24e116..67b48464c8 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt; import io.netty.handler.ssl.SslHandler; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.units.qual.C; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -26,11 +27,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; import org.thingsboard.server.common.transport.util.SslUtil; @@ -144,84 +143,37 @@ public class MqttSslHandlerProvider { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - String deviceCN = SslUtil.parseCommonName(chain[0]); String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); - String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); - String credentialsBody = null; - for (X509Certificate cert : chain) { - try { - String strCert = SslUtil.getCertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - final String[] credentialsBodyHolder = new String[1]; - CountDownLatch latch = new CountDownLatch(1); - transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - credentialsBodyHolder[0] = msg.getCredentials(); - latch.countDown(); - } else { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { - if (msg.isDeviceProfileFound()) { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(clientDeviceCertHash) - .setValue(clientDeviceCertValue) - .setCommonName(deviceCN) - .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) - .build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - credentialsBodyHolder[0] = msg.getCredentials(); - latch.countDown(); - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - } - ); - } else { - latch.countDown(); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - } + final String[] credentialsBodyHolder = new String[1]; + CountDownLatch latch = new CountDownLatch(1); + try { + String certificateChain = SslUtil.getCertificateChainString(chain); + transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg + .newBuilder().setCertificate(certificateChain).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + credentialsBodyHolder[0] = msg.getCredentials(); } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - latch.await(10, TimeUnit.SECONDS); - if (clientDeviceCertValue.equals(credentialsBodyHolder[0])) { - credentialsBody = credentialsBodyHolder[0]; - break; - } - } catch (InterruptedException | CertificateEncodingException e) { - log.error(e.getMessage(), e); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + // to fix this error, cuz no one can understand this ... + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + throw new CertificateException("Invalid Certificate's chain"); } - } - if (credentialsBody == null) { - throw new CertificateException("Invalid Certificate's chain"); + } catch (CertificateEncodingException | InterruptedException e) { + log.error(e.getMessage(), e); } } + } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 9159759c28..813a6f1ed2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -67,6 +67,7 @@ import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; @@ -851,7 +852,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { @@ -860,7 +861,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { - log.error(e.getMessage(), e); + log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); + ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); + ctx.close(); } }); } catch (Exception e) { @@ -873,9 +876,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private X509Certificate getX509Certificate() { try { - Certificate[] certChains = sslHandler.engine().getSession().getPeerCertificates(); - if (certChains.length > 1) { - return (X509Certificate) certChains[0]; + Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); + if (certChain.length > 1) { + return (X509Certificate) certChain[0]; } } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 21e668cf37..3e2e23e43a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -53,12 +52,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; -import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg; import java.util.List; import java.util.concurrent.ExecutorService; @@ -90,10 +88,7 @@ public interface TransportService { void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback); - void process(DeviceTransportType transportType, ValidateDeviceProfileX509CertRequestMsg msg, - TransportServiceCallback callback); - - void process(DeviceTransportType transportType, UpdateOrCreateDeviceX509CertRequestMsg msg, + void process(DeviceTransportType transportType, ValidateOrCreateDeviceX509CertRequestMsg msg, TransportServiceCallback callback); void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index a41c1d6055..4639ce0430 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -71,7 +71,6 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; @@ -429,35 +428,16 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceProfileX509CertRequestMsg requestMsg, - TransportServiceCallback callback) { - log.trace("Processing msg: {}", requestMsg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), - TransportApiRequestMsg.newBuilder().setValidateProfileX509CertRequestMsg(requestMsg).build()); - ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - TransportProtos.ValidateDeviceProfileCredentialsResponseMsg msg = tmp.getValue().getValidateDeviceProfileResponseMsg(); - ValidateDeviceProfileCredentialsResponse.ValidateDeviceProfileCredentialsResponseBuilder result = ValidateDeviceProfileCredentialsResponse.builder(); - DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB())); - result.deviceProfileId(deviceProfileId); - result.isDeviceProfileFound(msg.getIsDeviceProfileFound()); - return result.build(); - }, MoreExecutors.directExecutor()); - AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); - } - - @Override - public void process(DeviceTransportType transportType, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg requestMsg, - TransportServiceCallback callback) { - log.trace("Processing msg: {}", requestMsg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder() - .setUpdateOrCreateDeviceCertRequestMsg(requestMsg).build()); + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", msg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + public void process(DeviceTransportType transportType, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateOrCreateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index 51ed2cd948..bc6f38f42c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.util.Arrays; /** * @author Valerii Sosliuk @@ -42,6 +43,17 @@ public class SslUtil { return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); } + public static String getCertificateChainString(Certificate[] chain) + throws CertificateEncodingException { + String begin = "-----BEGIN CERTIFICATE-----"; + String end = "-----END CERTIFICATE-----"; + StringBuilder stringBuilder = new StringBuilder(); + for (Certificate cert: chain) { + stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLinesWithoutBeginEnd(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); + } + return stringBuilder.toString(); + } + public static String parseCommonName(X509Certificate certificate) { X500Name x500name; try { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java index b6758af59c..10363ac5d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java @@ -53,12 +53,4 @@ public interface DeviceCredentialsDao extends Dao { */ DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId); - /** - * Find device credentials by device name. - * - * @param deviceName the device name - * @return the device credentials object - */ - DeviceCredentials findByTenantIdAndDeviceName(TenantId tenantId, String deviceName); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index c21031fe65..138a8aaa9d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -83,13 +83,6 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService impl @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY) private String certificateRegexPattern; + @Column(name = ModelConstants.ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION) + private boolean allowCreateDevice; + public DeviceProfileEntity() { super(); } @@ -138,6 +141,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.certificateHash = deviceProfile.getCertificateHash(); this.certificateValue = deviceProfile.getCertificateValue(); this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); + this.allowCreateDevice = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -203,6 +207,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setCertificateHash(certificateHash); deviceProfile.setCertificateValue(certificateValue); deviceProfile.setCertificateRegexPattern(certificateRegexPattern); + deviceProfile.setAllowCreateNewDevicesByX509Strategy(allowCreateDevice); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 492ecf939a..0bf618e367 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -133,14 +133,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Tue, 24 Jan 2023 16:37:05 +0200 Subject: [PATCH 010/168] Fix CacheKey toString and validation logs --- .../transport/DefaultTransportApiService.java | 4 +-- common/cluster-api/src/main/proto/queue.proto | 1 + .../server/common/data/DeviceProfile.java | 8 ++--- .../mqtt/MqttSslHandlerProvider.java | 6 ++-- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../dao/device/DeviceProfileCacheKey.java | 8 +++-- .../dao/device/DeviceProfileEvictEvent.java | 1 + .../dao/device/DeviceProfileServiceImpl.java | 31 ++++++++++++++----- .../validator/DeviceProfileDataValidator.java | 13 ++++---- 9 files changed, 46 insertions(+), 28 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 0bf0ff8035..24f6de76d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -258,8 +258,7 @@ public class DefaultTransportApiService implements TransportApiService { } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); if (deviceProfile != null) { - String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); - String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern()); + String deviceName = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); if (device != null) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); @@ -275,7 +274,6 @@ public class DefaultTransportApiService implements TransportApiService { deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); return getDeviceInfo(deviceCredentials); } - } } } catch (CertificateEncodingException e) { diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index b9741429f7..9f62eca36b 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -422,6 +422,7 @@ message CredentialsDataProto { ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; + ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateDeviceX509CertRequestMsg = 4; } message ProvisionDeviceRequestMsg { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index b9b1053d99..e226261227 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,13 +66,13 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 16, value = "CA certificate value. ") + @ApiModelProperty(position = 18, value = "CA certificate value. ") private String certificateValue; - @ApiModelProperty(position = 17, value = "CA certificate hash. ") + @ApiModelProperty(position = 19, value = "CA certificate hash. ") private String certificateHash; - @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN. ") + @ApiModelProperty(position = 20, value = "Regex to fetch deviceName from CN. ") private String certificateRegexPattern; - @ApiModelProperty(position = 19, value = "Allow to create new devices by x509 provision strategy. ") + @ApiModelProperty(position = 21, value = "Allow to create new devices by x509 provision strategy. ") private boolean allowCreateNewDevicesByX509Strategy; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 67b48464c8..3224556e09 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.mqtt; import io.netty.handler.ssl.SslHandler; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.units.qual.C; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -161,14 +160,13 @@ public class MqttSslHandlerProvider { @Override public void onError(Throwable e) { - // to fix this error, cuz no one can understand this ... - log.error(e.getMessage(), e); + log.trace("Failed to process certificate chain: {}", certificateChain, e); latch.countDown(); } }); latch.await(10, TimeUnit.SECONDS); if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { - throw new CertificateException("Invalid Certificate's chain"); + throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials."); } } catch (CertificateEncodingException | InterruptedException e) { log.error(e.getMessage(), e); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 813a6f1ed2..00d318544f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -877,7 +877,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private X509Certificate getX509Certificate() { try { Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); - if (certChain.length > 1) { + if (certChain.length > 0) { return (X509Certificate) certChain[0]; } } catch (SSLPeerUnverifiedException e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index 228720f0e7..fb44092bd4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -56,14 +56,18 @@ public class DeviceProfileCacheKey implements Serializable { return new DeviceProfileCacheKey(tenantId, null, null, null, true); } + /** + * IMPORTANT: Method toString() has to return unique value, if you add additional field to this class, please also refactor toString(). + */ @Override public String toString() { if (deviceProfileId != null) { return deviceProfileId.toString(); } else if (defaultProfile) { return tenantId.toString(); - } else { - return tenantId + "_" + name; + } else if (certificateHash != null) { + return certificateHash; } + return tenantId + "_" + name; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index b5b4b27a6b..acd34ff8e5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -27,5 +27,6 @@ public class DeviceProfileEvictEvent { private final String oldName; private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; + private final String certificateHash; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 8cd08739b3..ffa25d16d6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -98,6 +98,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 0bf618e367..d737a48a9b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -138,7 +138,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator rootCa = new HashSet<>(); + boolean getRootCAFromJavaCacerts(String deviceProfileHash) { try { String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar); FileInputStream is = new FileInputStream(filename); @@ -408,12 +407,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Tue, 31 Jan 2023 18:04:45 +0200 Subject: [PATCH 011/168] Add unit tests and update regex pattern to convert chain to list --- .../transport/DefaultTransportApiService.java | 85 +++++---- .../BaseDeviceProfileControllerTest.java | 19 ++ .../DefaultTransportApiServiceTest.java | 174 ++++++++++++++++++ .../mqtt/AbstractMqttIntegrationTest.java | 7 + .../mqtt/MqttTestConfigProperties.java | 5 + common/cluster-api/src/main/proto/queue.proto | 1 - .../server/common/msg/EncryptionUtil.java | 6 +- .../mqtt/MqttSslHandlerProvider.java | 3 +- .../server/common/transport/util/SslUtil.java | 3 +- .../dao/device/DeviceProfileServiceImpl.java | 27 ++- 10 files changed, 272 insertions(+), 58 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 24f6de76d7..f434df2faa 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -107,11 +107,9 @@ import org.thingsboard.server.service.resource.TbResourceService; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -243,41 +241,44 @@ public class DefaultTransportApiService implements TransportApiService { } } - private ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + List chain = convertX509CertificateChainToList(certChain); + String updateDeviceCertificateValue = chain.get(0); + String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); + String deviceCommonName = ""; try { - List chain = getX509CertificateChainFromString(certChain); - String updateDeviceCertificateValue = SslUtil.getCertificateString(chain.get(0)); - String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); - - for (X509Certificate cert: chain) { - String certificateValue = SslUtil.getCertificateString(cert); - String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); - if (credentials != null && credentials.getCredentialsType() == credentialsType) { - return getDeviceInfo(credentials); + deviceCommonName = SslUtil.parseCommonName(readCertFile(chain.get(0))); + } catch (Exception ignored) { + } + for (String certificateValue : chain) { + String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); + if (credentials != null && credentials.getCredentialsType() == credentialsType) { + return getDeviceInfo(credentials); + } + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); + if (deviceProfile != null) { + String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, deviceProfile.getCertificateRegexPattern()); + if (deviceName == null) { + log.error("Device name cannot be unmatched from CN!"); + return getEmptyTransportApiResponseFuture(); } - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); - if (deviceProfile != null) { - String deviceName = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); - Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); - if (device != null) { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { - deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); - } else if (deviceCredentials == null) { - deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); - } - return getDeviceInfo(deviceCredentials); - } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { - Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); - deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); - return getDeviceInfo(deviceCredentials); + Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); + if (device != null) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { + deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + } else if (deviceCredentials == null) { + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); } + return getDeviceInfo(deviceCredentials); + } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { + Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); + deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + return getDeviceInfo(deviceCredentials); } } - } catch (CertificateEncodingException e) { - throw new RuntimeException(e); } return getEmptyTransportApiResponseFuture(); } @@ -727,19 +728,17 @@ public class DefaultTransportApiService implements TransportApiService { if (matcher.find()) { return matcher.group(0); } - return commonName; + return null; } - private List getX509CertificateChainFromString(String certificateChain) { - List chain = new ArrayList<>(); - String[] test = Arrays.stream(certificateChain.split("-----BEGIN CERTIFICATE-----")).filter(e -> e.trim().length() > 0).map(EncryptionUtil::certTrimNewLines).toArray(String[]::new); - Arrays.stream(test).forEach(s -> { - try { - chain.add(readCertFile(s)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + private List convertX509CertificateChainToList(String certificateChain) { + List chain = new ArrayList<>(); + String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(certificateChain); + while (matcher.find()) { + chain.add(EncryptionUtil.certTrimNewLines(matcher.group())); + } return chain; } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 3fe80b1112..43b8e51025 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -298,6 +298,25 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } + @Test + public void testSaveDeviceProfileWithSameCertificateHash() throws Exception { + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setCertificateHash("Certificate Hash"); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + deviceProfile2.setCertificateHash("Certificate Hash"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Device profile with such certificate hash already exists"; + doPost("/api/deviceProfile", deviceProfile2) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + } + @Test public void testChangeDeviceProfileTypeNull() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java new file mode 100644 index 0000000000..4a2ff07978 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -0,0 +1,174 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.transport; + + +import com.google.common.util.concurrent.Futures; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceProvisionService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.resource.TbResourceService; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = DefaultTransportApiService.class) +public class DefaultTransportApiServiceTest { + + @MockBean + protected TbDeviceProfileCache deviceProfileCache; + @MockBean + protected TbTenantProfileCache tenantProfileCache; + @MockBean + protected TbApiUsageStateService apiUsageStateService; + @MockBean + protected DeviceService deviceService; + @MockBean + protected DeviceProfileService deviceProfileService; + @MockBean + protected RelationService relationService; + @MockBean + protected DeviceCredentialsService deviceCredentialsService; + @MockBean + protected DbCallbackExecutorService dbCallbackExecutorService; + @MockBean + protected TbClusterService tbClusterService; + @MockBean + protected DataDecodingEncodingService dataDecodingEncodingService; + @MockBean + protected DeviceProvisionService deviceProvisionService; + @MockBean + protected TbResourceService resourceService; + @MockBean + protected OtaPackageService otaPackageService; + @MockBean + protected OtaPackageDataCache otaPackageDataCache; + @MockBean + protected QueueService queueService; + @SpyBean + DefaultTransportApiService service; + + private final String deviceCertificate = "-----BEGIN CERTIFICATE-----Device certificate value-----END CERTIFICATE-----"; + private final String deviceProfileCertificate = "-----BEGIN CERTIFICATE-----Device profile certificate value-----END CERTIFICATE-----"; + + @Test + public void validateExistingDeviceX509Certificate() { + var device = createDevice(); + when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); + + var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(deviceCredentials); + + service.validateOrCreateDeviceX509Certificate(deviceCertificate, DeviceCredentialsType.X509_CERTIFICATE); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); + } + + @Test + public void updateExistingDeviceX509Certificate() { + var deviceProfile = createDeviceProfile(deviceProfileCertificate); + when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); + + var device = createDevice(); + when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device); + when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); + + var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + + service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); + verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + } + + @Test + public void createDeviceByX509Provision() { + var deviceProfile = createDeviceProfile(deviceProfileCertificate); + when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); + + var device = createDevice(); + when(deviceService.saveDevice(any())).thenReturn(device); + when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); + + var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + + service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); + verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + } + + private DeviceCredentials createDeviceCredentials(String certificateValue, DeviceId deviceId) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setDeviceId(deviceId); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash(certificateValue)); + deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + return deviceCredentials; + } + + private DeviceProfile createDeviceProfile(String certificateValue) { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setCertificateValue(certificateValue); + deviceProfile.setCertificateHash(EncryptionUtil.getSha3Hash(certificateValue)); + deviceProfile.setCertificateRegexPattern("^$"); + deviceProfile.setAllowCreateNewDevicesByX509Strategy(true); + deviceProfile.setProvisionType(DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE); + return deviceProfile; + } + + private Device createDevice() { + Device device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + return device; + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 917e41d1b4..69b1f87502 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.AllowCreatingNewDevicesByX509CertificateProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -94,6 +95,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg deviceProfile.setProvisionType(provisionType); deviceProfile.setProvisionDeviceKey(config.getProvisionKey()); deviceProfile.setDescription(transportPayloadType.name() + " Test"); + deviceProfile.setAllowCreateNewDevicesByX509Strategy(config.allowCreatingNewDeviceByX509Strategy); + deviceProfile.setCertificateValue("Device Profile certificate value"); + deviceProfile.setCertificateRegexPattern(config.getRegEx()); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); @@ -144,6 +148,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg case CHECK_PRE_PROVISIONED_DEVICES: provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); break; + case ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE: + provisionConfiguration = new AllowCreatingNewDevicesByX509CertificateProvisionConfiguration(config.getProvisionSecret()); + break; case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(config.getProvisionSecret()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java index bc535b424b..4c57f413c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java @@ -40,9 +40,14 @@ public class MqttTestConfigProperties { boolean enableCompatibilityWithJsonPayloadFormat; boolean useJsonPayloadFormatForDefaultDownlinkTopics; boolean sendAckOnValidationException; + boolean allowCreatingNewDeviceByX509Strategy; DeviceProfileProvisionType provisionType; String provisionKey; String provisionSecret; + String x509DeviceCertificate; + String x509DeviceProfileCertificate; + String commonName; + String regEx; } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 9f62eca36b..b9741429f7 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -422,7 +422,6 @@ message CredentialsDataProto { ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; - ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateDeviceX509CertRequestMsg = 4; } message ProvisionDeviceRequestMsg { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index 6bccac1591..4cdd61eeec 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -35,9 +35,11 @@ public class EncryptionUtil { .replaceAll("-----END CERTIFICATE-----", ""); } - public static String certTrimNewLinesWithoutBeginEnd(String input) { + public static String certTrimNewLinesForChainInDeviceProfile(String input) { return input.replaceAll("\n", "") - .replaceAll("\r", ""); + .replaceAll("\r", "") + .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n"); } public static String pubkTrimNewLines(String input) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 3224556e09..d73743e6b5 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -41,7 +41,6 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.CountDownLatch; @@ -168,7 +167,7 @@ public class MqttSslHandlerProvider { if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials."); } - } catch (CertificateEncodingException | InterruptedException e) { + } catch (Exception e) { log.error(e.getMessage(), e); } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index bc6f38f42c..4245cae349 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.util.Arrays; /** * @author Valerii Sosliuk @@ -49,7 +48,7 @@ public class SslUtil { String end = "-----END CERTIFICATE-----"; StringBuilder stringBuilder = new StringBuilder(); for (Certificate cert: chain) { - stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLinesWithoutBeginEnd(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); + stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); } return stringBuilder.toString(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index ffa25d16d6..48e6a5b6b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -48,6 +48,10 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import java.io.ByteArrayInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -342,17 +346,15 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1; + private String formatCertificateValue(String certificateValue) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ByteArrayInputStream inputStream = new ByteArrayInputStream(certificateValue.getBytes()); + Certificate[] certificates = cf.generateCertificates(inputStream).toArray(new Certificate[0]); + if (certificates.length > 1) { + return EncryptionUtil.certTrimNewLinesForChainInDeviceProfile(certificateValue); + } + return EncryptionUtil.certTrimNewLines(certificateValue); + } catch (CertificateException e) { + throw new RuntimeException(e); + } } } From 0943b80d8e4f074950648b3e0047462e037699f8 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 1 Feb 2023 16:49:00 +0200 Subject: [PATCH 012/168] Fix schema and micro refactoring --- .../main/data/upgrade/3.4.3/schema_update.sql | 3 --- .../transport/DefaultTransportApiService.java | 11 ++++++--- .../DefaultTransportApiServiceTest.java | 15 ++++++++---- .../mqtt/AbstractMqttIntegrationTest.java | 7 ------ .../mqtt/MqttTestConfigProperties.java | 5 ---- common/cluster-api/src/main/proto/queue.proto | 2 +- .../server/common/data/DeviceProfile.java | 11 +-------- .../data/DeviceProfileProvisionType.java | 2 +- .../DeviceProfileProvisionConfiguration.java | 2 +- ...rtificateChainProvisionConfiguration.java} | 11 ++++++--- .../mqtt/MqttSslHandlerProvider.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 23 ++++++++++++------- .../dao/model/sql/DeviceProfileEntity.java | 15 ------------ .../main/resources/sql/schema-entities.sql | 3 --- 14 files changed, 47 insertions(+), 65 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java => X509CertificateChainProvisionConfiguration.java} (67%) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 78203951ec..2aaa574bb4 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -31,9 +31,6 @@ CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id) ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS certificate_hash varchar, - ADD COLUMN IF NOT EXISTS certificate_value varchar, - ADD COLUMN IF NOT EXISTS certificate_regex_pattern varchar(255), - ADD COLUMN IF NOT EXISTS allow_create_device_by_x509 boolean, DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index f434df2faa..6a3780d529 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfig import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -173,7 +174,7 @@ public class DefaultTransportApiService implements TransportApiService { result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasValidateOrCreateX509CertRequestMsg()) { TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateOrCreateX509CertRequestMsg(); - result = validateOrCreateDeviceX509Certificate(msg.getCertificate(), DeviceCredentialsType.X509_CERTIFICATE); + result = validateOrCreateDeviceX509Certificate(msg.getCertificateChain(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -258,7 +259,11 @@ public class DefaultTransportApiService implements TransportApiService { } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); if (deviceProfile != null) { - String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, deviceProfile.getCertificateRegexPattern()); + X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration(); + if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + } + String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, x509Configuration.getCertificateRegExPattern()); if (deviceName == null) { log.error("Device name cannot be unmatched from CN!"); return getEmptyTransportApiResponseFuture(); @@ -272,7 +277,7 @@ public class DefaultTransportApiService implements TransportApiService { deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); } return getDeviceInfo(deviceCredentials); - } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { + } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN && x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index 4a2ff07978..71a2f8b70c 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -29,6 +29,9 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -158,11 +161,15 @@ public class DefaultTransportApiServiceTest { private DeviceProfile createDeviceProfile(String certificateValue) { DeviceProfile deviceProfile = new DeviceProfile(); - deviceProfile.setCertificateValue(certificateValue); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); + provision.setCertificateValue(certificateValue); + provision.setCertificateRegExPattern("^$"); + provision.setAllowCreateNewDevicesByX509Certificate(true); + deviceProfileData.setProvisionConfiguration(provision); + deviceProfile.setProfileData(deviceProfileData); deviceProfile.setCertificateHash(EncryptionUtil.getSha3Hash(certificateValue)); - deviceProfile.setCertificateRegexPattern("^$"); - deviceProfile.setAllowCreateNewDevicesByX509Strategy(true); - deviceProfile.setProvisionType(DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE); + deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); return deviceProfile; } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 69b1f87502..917e41d1b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.AllowCreatingNewDevicesByX509CertificateProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -95,9 +94,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg deviceProfile.setProvisionType(provisionType); deviceProfile.setProvisionDeviceKey(config.getProvisionKey()); deviceProfile.setDescription(transportPayloadType.name() + " Test"); - deviceProfile.setAllowCreateNewDevicesByX509Strategy(config.allowCreatingNewDeviceByX509Strategy); - deviceProfile.setCertificateValue("Device Profile certificate value"); - deviceProfile.setCertificateRegexPattern(config.getRegEx()); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); @@ -148,9 +144,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg case CHECK_PRE_PROVISIONED_DEVICES: provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); break; - case ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE: - provisionConfiguration = new AllowCreatingNewDevicesByX509CertificateProvisionConfiguration(config.getProvisionSecret()); - break; case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(config.getProvisionSecret()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java index 4c57f413c9..bc535b424b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java @@ -40,14 +40,9 @@ public class MqttTestConfigProperties { boolean enableCompatibilityWithJsonPayloadFormat; boolean useJsonPayloadFormatForDefaultDownlinkTopics; boolean sendAckOnValidationException; - boolean allowCreatingNewDeviceByX509Strategy; DeviceProfileProvisionType provisionType; String provisionKey; String provisionSecret; - String x509DeviceCertificate; - String x509DeviceProfileCertificate; - String commonName; - String regEx; } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index b9741429f7..6e9ec38be8 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -170,7 +170,7 @@ message ValidateDeviceX509CertRequestMsg { } message ValidateOrCreateDeviceX509CertRequestMsg { - string certificate = 1; + string certificateChain = 1; } message ValidateBasicMqttCredRequestMsg { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index e226261227..05f8ae19ba 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,14 +66,8 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 18, value = "CA certificate value. ") - private String certificateValue; - @ApiModelProperty(position = 19, value = "CA certificate hash. ") + @ApiModelProperty(position = 18, value = "CA certificate hash. ") private String certificateHash; - @ApiModelProperty(position = 20, value = "Regex to fetch deviceName from CN. ") - private String certificateRegexPattern; - @ApiModelProperty(position = 21, value = "Allow to create new devices by x509 provision strategy. ") - private boolean allowCreateNewDevicesByX509Strategy; @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + @@ -131,9 +125,6 @@ public class DeviceProfile extends SearchTextBased implements H this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); - this.allowCreateNewDevicesByX509Strategy = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); - this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); - this.certificateValue = deviceProfile.getCertificateValue(); this.certificateHash = deviceProfile.getCertificateHash(); this.externalId = deviceProfile.getExternalId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java index 7ee03d7227..f2daf71cca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java @@ -19,5 +19,5 @@ public enum DeviceProfileProvisionType { DISABLED, ALLOW_CREATE_NEW_DEVICES, CHECK_PRE_PROVISIONED_DEVICES, - ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE + X509_CERTIFICATE_CHAIN } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java index 7a70c00b98..048e10739f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -32,7 +32,7 @@ import java.io.Serializable; @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"), @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"), @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES"), - @JsonSubTypes.Type(value = AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.class, name = "ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE")}) + @JsonSubTypes.Type(value = X509CertificateChainProvisionConfiguration.class, name = "X509_CERTIFICATE_CHAIN")}) public interface DeviceProfileProvisionConfiguration extends Serializable { String getProvisionDeviceSecret(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java similarity index 67% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java index e84d09829f..0b500f3e52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @Data -public class AllowCreatingNewDevicesByX509CertificateProvisionConfiguration implements DeviceProfileProvisionConfiguration { +@NoArgsConstructor +public class X509CertificateChainProvisionConfiguration implements DeviceProfileProvisionConfiguration { - private final String provisionDeviceSecret; + private String provisionDeviceSecret; + private String certificateValue; + private String certificateRegExPattern; + private boolean allowCreateNewDevicesByX509Certificate; @Override public DeviceProfileProvisionType getType() { - return DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE; + return DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index d73743e6b5..d5ff77837b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -147,7 +147,7 @@ public class MqttSslHandlerProvider { try { String certificateChain = SslUtil.getCertificateChainString(chain); transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg - .newBuilder().setCertificate(certificateChain).build(), + .newBuilder().setCertificateChain(certificateChain).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 48e6a5b6b6..f558eb2c7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; @@ -134,8 +135,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService impl @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) private UUID externalId; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY) - private String certificateValue; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY) private String certificateHash; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY) - private String certificateRegexPattern; - - @Column(name = ModelConstants.ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION) - private boolean allowCreateDevice; - public DeviceProfileEntity() { super(); } @@ -139,9 +130,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); this.certificateHash = deviceProfile.getCertificateHash(); - this.certificateValue = deviceProfile.getCertificateValue(); - this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); - this.allowCreateDevice = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -205,9 +193,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); deviceProfile.setCertificateHash(certificateHash); - deviceProfile.setCertificateValue(certificateValue); - deviceProfile.setCertificateRegexPattern(certificateRegexPattern); - deviceProfile.setAllowCreateNewDevicesByX509Strategy(allowCreateDevice); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index eed6f237b6..bd61d9a7c3 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -292,12 +292,9 @@ CREATE TABLE IF NOT EXISTS device_profile ( default_dashboard_id uuid, default_queue_name varchar(255), provision_device_key varchar, - certificate_value varchar, certificate_hash varchar, - certificate_regex_pattern varchar(255), default_edge_rule_chain_id uuid, external_id uuid, - allow_create_device_by_x509 boolean, CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash), CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), From 058c97e4de6e6af470a4e5d7dae661253f6bb4e7 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 2 Feb 2023 10:20:24 +0200 Subject: [PATCH 013/168] Fix headers and remove extra constants from Model Constants --- .../service/transport/DefaultTransportApiServiceTest.java | 5 ++--- .../profile/X509CertificateChainProvisionConfiguration.java | 2 +- .../server/dao/device/DeviceProfileServiceImpl.java | 3 +-- .../org/thingsboard/server/dao/model/ModelConstants.java | 3 --- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index 71a2f8b70c..0309bec131 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,9 +29,8 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java index 0b500f3e52..9257053b4f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 9d5c0608d3..767fd805cf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -379,9 +379,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1) { return EncryptionUtil.certTrimNewLinesForChainInDeviceProfile(certificateValue); } - return EncryptionUtil.certTrimNewLines(certificateValue); } catch (CertificateException ignored) {} - return certificateValue; + return EncryptionUtil.certTrimNewLines(certificateValue); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 76a5423359..7b0ca50d0d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -186,10 +186,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id"; public static final String DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY = "certificate_hash"; - public static final String DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY = "certificate_value"; - public static final String DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY = "certificate_regex_pattern"; public static final String DEVICE_PROFILE_DEFAULT_EDGE_RULE_CHAIN_ID_PROPERTY = "default_edge_rule_chain_id"; - public static final String ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION = "allow_create_device_by_x509"; /** * Asset profile constants. From 5bf7b6162e95ece7e2a7a6607e64c6ffb1381fb6 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 10 Mar 2023 15:48:52 +0200 Subject: [PATCH 014/168] Fix sql schema --- application/src/main/data/upgrade/3.4.4/schema_update.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 9f8a81daaf..d208585ed7 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -353,7 +353,7 @@ $$; ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS certificate_hash varchar, -DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, + DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); -- DEVICE PROFILE CERTIFICATE END From 0e019967e2e45bdc33139e80b1fb720ae53c1e78 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 22 Mar 2023 12:13:59 +0200 Subject: [PATCH 015/168] Feature X509 Device provision added ui form --- ...ile-provision-configuration.component.html | 36 ++++++++++++++++--- ...ofile-provision-configuration.component.ts | 16 ++++++++- ui-ngx/src/app/shared/models/device.models.ts | 6 ++-- .../assets/locale/locale.constant-en_US.json | 13 +++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index b588a5ec34..ffe4a4732a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -27,18 +27,20 @@ {{ 'device-profile.provision-strategy-required' | translate }} -

+
device-profile.provision-device-key - + --> {{ 'device-profile.provision-device-key-required' | translate }} @@ -46,17 +48,41 @@ device-profile.provision-device-secret - + --> {{ 'device-profile.provision-device-secret-required' | translate }}
+
+
+ + {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} + +
+ + device-profile.provision-strategy-x509.certificate-value + + + {{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }} + + + + device-profile.provision-strategy-x509.cn-regex-variable + + + {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 872571dc20..17ccd40df0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -86,7 +86,10 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup = this.fb.group({ type: [DeviceProvisionType.DISABLED, Validators.required], provisionDeviceSecret: [{value: null, disabled: true}, Validators.required], - provisionDeviceKey: [{value: null, disabled: true}, Validators.required] + provisionDeviceKey: [{value: null, disabled: true}, Validators.required], + certificateValue: [{value: null, disabled: true}, Validators.required], + certificateRegExPattern: [{value: '[\\w]*', disabled: true}, Validators.required], + allowCreateNewDevicesByX509Certificate: [{value: true, disabled: true}] }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { @@ -94,6 +97,17 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null, {emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); + this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateValue').patchValue(null); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue(null); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(null); + + } else if (type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); } else { const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; if (!provisionDeviceSecret || !provisionDeviceSecret.length) { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index ae601b0039..382b32799b 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -62,7 +62,8 @@ export enum CoapTransportDeviceType { export enum DeviceProvisionType { DISABLED = 'DISABLED', ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES', - CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES' + CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES', + X509_CERTIFICATE_CHAIN = 'X509_CERTIFICATE_CHAIN' } export interface DeviceConfigurationFormInfo { @@ -110,7 +111,8 @@ export const deviceProvisionTypeTranslationMap = new MapMore", + "allow-create-new-devices": "Create new devices", + "hint-allow-create-new-devices": "Hint: if selected new devices will be created with correct device credentials from client certificate.", + "certificate-value": "Certificate in PEM format", + "certificate-value-required": "Certificate in PEM format is required", + "cn-regex-variable": "CN Regular Expression variable", + "cn-regex-variable-required": "CN Regular Expression variable is required", + "hint-cn-regex-variable-title": "Examples of RegEx usage:", + "hint-cn-regex-variable-examples": "1. Pattern: .* - matches any character (until line terminators)
CN sample: DeviceName\\nAdditionalInfo
Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the “@” symbol (@ could be replaced by any other symbol)
CN sample: DeviceName@AdditionalInfo
Pattern matches: DeviceName

3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
CN sample: AdditionalInfo2110#DeviceName_01
Pattern matches: DeviceNane_01", + "hint-cn-regex-variable-note": "Note: Client will get error response in case regex is failed to match." + }, "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From d3803f828dba5b90545f47ecf7280caee71cb2e5 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 22 Mar 2023 13:51:56 +0200 Subject: [PATCH 016/168] Device profile provision strategy -> fix patch values form for X509 Certificate Chain --- ...ile-provision-configuration.component.html | 13 ++++++------- ...ofile-provision-configuration.component.ts | 19 ++++++++++++++++--- .../profile/device-profile.component.ts | 10 ++++++++-- ui-ngx/src/app/shared/models/device.models.ts | 3 +++ .../assets/locale/locale.constant-en_US.json | 5 ++--- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index ffe4a4732a..8b44586f50 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -33,14 +33,14 @@ device-profile.provision-device-key - + {{ 'device-profile.provision-device-key-required' | translate }} @@ -48,14 +48,14 @@ device-profile.provision-device-secret - + {{ 'device-profile.provision-device-secret-required' | translate }} @@ -80,9 +80,8 @@ {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} + device-profile.provision-strategy-x509.hint-cn-regex-variable -
-
-
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 17ccd40df0..bb936080da 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -88,8 +88,8 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu provisionDeviceSecret: [{value: null, disabled: true}, Validators.required], provisionDeviceKey: [{value: null, disabled: true}, Validators.required], certificateValue: [{value: null, disabled: true}, Validators.required], - certificateRegExPattern: [{value: '[\\w]*', disabled: true}, Validators.required], - allowCreateNewDevicesByX509Certificate: [{value: true, disabled: true}] + certificateRegExPattern: [{value: null, disabled: true}, Validators.required], + allowCreateNewDevicesByX509Certificate: [{value: null, disabled: true}] }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { @@ -103,8 +103,19 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue(null); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(null); - } else if (type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + const certificateValue: string = this.provisionConfigurationFormGroup.get('certificateValue').value; + if (!certificateValue || !certificateValue.length) { + this.provisionConfigurationFormGroup.get('certificateValue').patchValue(null, {emitEvent: false}); + } + const certificateRegExPattern: string = this.provisionConfigurationFormGroup.get('certificateRegExPattern').value; + if (!certificateRegExPattern || !certificateRegExPattern.length) { + this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue('[\\w]*', {emitEvent: false}); + } + const allowCreateNewDevicesByX509Certificate: boolean | null = this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').value; + if (typeof allowCreateNewDevicesByX509Certificate !== 'boolean') { + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(true, {emitEvent: false}); + } this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); @@ -119,6 +130,8 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); } }); this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 9adcf9c168..9a6d150ac1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -104,7 +104,10 @@ export class DeviceProfileComponent extends EntityComponent { const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, - provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret + provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, + certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, + certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, + allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; const form = this.fb.group( { @@ -185,7 +188,10 @@ export class DeviceProfileComponent extends EntityComponent { const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, - provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret + provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, + certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, + certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, + allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 382b32799b..de240934c4 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -322,6 +322,9 @@ export interface DeviceProvisionConfiguration { type: DeviceProvisionType; provisionDeviceSecret?: string; provisionDeviceKey?: string; + certificateValue?: string; + certificateRegExPattern?: string; + allowCreateNewDevicesByX509Certificate?: boolean; } export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index d6e70ddd51..09ae98d682 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1504,9 +1504,8 @@ "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", "cn-regex-variable-required": "CN Regular Expression variable is required", - "hint-cn-regex-variable-title": "Examples of RegEx usage:", - "hint-cn-regex-variable-examples": "1. Pattern: .* - matches any character (until line terminators)
CN sample: DeviceName\\nAdditionalInfo
Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the “@” symbol (@ could be replaced by any other symbol)
CN sample: DeviceName@AdditionalInfo
Pattern matches: DeviceName

3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
CN sample: AdditionalInfo2110#DeviceName_01
Pattern matches: DeviceNane_01", - "hint-cn-regex-variable-note": "Note: Client will get error response in case regex is failed to match." + "hint-cn-regex-variable": "Required to fetch device name from device's X509 certificate's common name.", + "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceNane_01
Note: Client will get error response in case regex is failed to match." }, "condition": "Condition", "condition-type": "Condition type", From 86423fdc7b2e2e3755e61efd172b2303de5b0c67 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 22 Mar 2023 17:31:19 +0200 Subject: [PATCH 017/168] Device profile provision X509 Certificate Chain added dynamic readMore for hint; updated text in hint --- ...ice-profile-provision-configuration.component.html | 11 +++++++---- ...evice-profile-provision-configuration.component.ts | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 8 +++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 8b44586f50..99cddbd576 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -27,13 +27,13 @@ {{ 'device-profile.provision-strategy-required' | translate }} -
device-profile.provision-device-key - <
-
+ {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index bb936080da..687bc069b1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -62,6 +62,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu deviceProvisionType = DeviceProvisionType; deviceProvisionTypes = Object.keys(DeviceProvisionType); deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap; + readMore: boolean; private requiredValue: boolean; get required(): boolean { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 09ae98d682..e9e839ce8b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -61,7 +61,9 @@ "done": "Done", "print": "Print", "restore": "Restore", - "confirm": "Confirm" + "confirm": "Confirm", + "more": "More", + "less": "Less" }, "aggregation": { "aggregation": "Aggregation", @@ -1497,7 +1499,7 @@ "provision-secret-copied-message": "Provision secret has been copied to clipboard", "provision-strategy-x509": { "certificate-chain": "X509 Certificates Chain", - "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN)... More", + "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN).

Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain.

Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known Certificate Authority (CA).", "allow-create-new-devices": "Create new devices", "hint-allow-create-new-devices": "Hint: if selected new devices will be created with correct device credentials from client certificate.", "certificate-value": "Certificate in PEM format", @@ -1505,7 +1507,7 @@ "cn-regex-variable": "CN Regular Expression variable", "cn-regex-variable-required": "CN Regular Expression variable is required", "hint-cn-regex-variable": "Required to fetch device name from device's X509 certificate's common name.", - "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceNane_01
Note: Client will get error response in case regex is failed to match." + "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceName_01
Note: Client will get error response in case regex is failed to match." }, "condition": "Condition", "condition-type": "Condition type", From 8703a2083efd9de64e51e03ddae7271be43b9d27 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 11:21:44 +0200 Subject: [PATCH 018/168] Device provision feature X509: 1. Added ng-templates with ngSwitchCase attribute 2. Fix NG0300: Multiple components match node with tagname button 3. Fix max-width for tb-hints --- ...ile-provision-configuration.component.html | 130 ++++++++++-------- ...ofile-provision-configuration.component.ts | 4 +- 2 files changed, 72 insertions(+), 62 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 99cddbd576..3b2b92ce98 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -27,64 +27,74 @@ {{ 'device-profile.provision-strategy-required' | translate }} -
- - device-profile.provision-device-key - - - - {{ 'device-profile.provision-device-key-required' | translate }} - - - - device-profile.provision-device-secret - - - - {{ 'device-profile.provision-device-secret-required' | translate }} - - -
-
- - - {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} - -
- - device-profile.provision-strategy-x509.certificate-value - - - {{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }} - - - - device-profile.provision-strategy-x509.cn-regex-variable - - - {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} - - device-profile.provision-strategy-x509.hint-cn-regex-variable - -
-
+
+ + + + + + + + + + {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} + +
+ + device-profile.provision-strategy-x509.certificate-value + + + {{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }} + + + + device-profile.provision-strategy-x509.cn-regex-variable + + + {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} + + device-profile.provision-strategy-x509.hint-cn-regex-variable + +
+
+
+ +
+ + device-profile.provision-device-key + + + + {{ 'device-profile.provision-device-key-required' | translate }} + + + + device-profile.provision-device-secret + + + + {{ 'device-profile.provision-device-secret-required' | translate }} + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 687bc069b1..beab4828a5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -32,7 +32,7 @@ import { DeviceProvisionType, deviceProvisionTypeTranslationMap } from '@shared/models/device.models'; -import { generateSecret, isDefinedAndNotNull } from '@core/utils'; +import { generateSecret, isBoolean, isDefinedAndNotNull } from '@core/utils'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -114,7 +114,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue('[\\w]*', {emitEvent: false}); } const allowCreateNewDevicesByX509Certificate: boolean | null = this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').value; - if (typeof allowCreateNewDevicesByX509Certificate !== 'boolean') { + if (isBoolean(allowCreateNewDevicesByX509Certificate)) { this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(true, {emitEvent: false}); } this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); From d603e7dbb709c59f0ba4143c869eeabff9b1dde3 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 11:52:41 +0200 Subject: [PATCH 019/168] Device profile Transport configuration MQTT profile set max-width tb-hint --- .../mqtt-device-profile-transport-configuration.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 1ef7892ecb..9e3ae02098 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -163,6 +163,6 @@ {{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} -
+ From 1498f264f68f2252c0f2f7ee4eaa1cd6824db390 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 12:20:20 +0200 Subject: [PATCH 020/168] MQTT device profile fix tb-hint --- .../mqtt-device-profile-transport-configuration.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 9e3ae02098..4dc8b2a7b8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -163,6 +163,6 @@ {{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} - +
From a3615f11fe1c286da9eab4444168ba03873ebd5a Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 14:03:06 +0200 Subject: [PATCH 021/168] Device profile feature X509: - fix edit form after creation; - added fields iteration in provisionConfigurationFormGroup for type DISABLED; --- ...ofile-provision-configuration.component.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index beab4828a5..465588fa24 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -94,16 +94,13 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null, {emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); - this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateValue').patchValue(null); - this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue(null); - this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(null); + for (const field in this.provisionConfigurationFormGroup.controls) { + if (field !== 'type') { + const control = this.provisionConfigurationFormGroup.get(field); + control.disable({emitEvent: false}); + control.patchValue(null, {emitEvent: false}); + } + } } else if (type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { const certificateValue: string = this.provisionConfigurationFormGroup.get('certificateValue').value; if (!certificateValue || !certificateValue.length) { @@ -114,12 +111,14 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue('[\\w]*', {emitEvent: false}); } const allowCreateNewDevicesByX509Certificate: boolean | null = this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').value; - if (isBoolean(allowCreateNewDevicesByX509Certificate)) { + if (!isBoolean(allowCreateNewDevicesByX509Certificate)) { this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(true, {emitEvent: false}); } this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); } else { const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; if (!provisionDeviceSecret || !provisionDeviceSecret.length) { @@ -133,6 +132,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); } }); this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { From f43e543e844a931df2a7882445c3e01268ed7d3a Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 23 Mar 2023 14:21:31 +0200 Subject: [PATCH 022/168] UI: color picker --- ui-ngx/package.json | 1 + .../color-picker-input.component.html | 18 +++ .../color-picker-input.component.ts | 70 +++++++++++ .../color-picker/color-picker.component.html | 37 ++++++ .../color-picker/color-picker.component.scss | 110 ++++++++++++++++++ .../color-picker/color-picker.component.ts | 106 +++++++++++++++++ .../dialog/color-picker-dialog.component.html | 13 ++- .../dialog/color-picker-dialog.component.ts | 18 ++- ui-ngx/src/app/shared/shared.module.ts | 11 +- ui-ngx/yarn.lock | 7 ++ 10 files changed, 380 insertions(+), 11 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker.component.html create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts diff --git a/ui-ngx/package.json b/ui-ngx/package.json index a8abb6fbeb..96577444fd 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -31,6 +31,7 @@ "@flowjs/flow.js": "^2.14.1", "@flowjs/ngx-flow": "~0.6.0", "@geoman-io/leaflet-geoman-free": "^2.13.0", + "@iplab/ngx-color-picker": "^15.0.2", "@juggle/resize-observer": "^3.3.1", "@mat-datetimepicker/core": "~11.0.3", "@material-ui/core": "4.12.3", diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html new file mode 100644 index 0000000000..564f1d64f2 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html @@ -0,0 +1,18 @@ +
+
+ + + + + + + + + +
+ + + + +
+ diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts new file mode 100644 index 0000000000..22ce2c0849 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts @@ -0,0 +1,70 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnInit, + Output +} from '@angular/core'; +import { Color } from '@iplab/ngx-color-picker'; +import { ColorType } from '@shared/components/color-picker/color-picker.component'; + +@Component({ + selector: `tb-color-picker-input-component`, + templateUrl: `./color-picker-input.component.html`, + styleUrls: [], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ColorPickerInputComponent { + + @Input() + public hue: Color; + + @Output() + public hueChange = new EventEmitter(false); + + @Input() + public color: Color; + + @Output() + public colorChange = new EventEmitter(false); + + public labelVisible: boolean; + + @Input() + public set label(value) { + this.labelVisible = true; + } + + @Input() + public colorFormat: string; + + public isAlphaVisible = true; + + @Input() + public set alpha(isVisible: boolean) { + this.isAlphaVisible = isVisible; + } + + public get value() { + return this.color ? this.color.getRgba() : null; + } + + constructor() { + } + + public onInputChange(newValue, color: 'R' | 'G' | 'B' | 'A') { + const value = this.value; + const red = color === 'R' ? newValue : value.red; + const green = color === 'G' ? newValue : value.green; + const blue = color === 'B' ? newValue : value.blue; + const alpha = color === 'A' ? newValue : value.alpha; + + const newColor = new Color().setRgba(red, green, blue, alpha); + const hue = new Color().setHsva(newColor.getHsva().hue); + + this.hueChange.emit(hue); + this.colorChange.emit(newColor); + } +} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html new file mode 100644 index 0000000000..dba4605736 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html @@ -0,0 +1,37 @@ + + +
+
+
+ +
+
+ + +
+
+ + + + + {{type}} + + + + + + + + + + + + + + + + + +
+ + diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss new file mode 100644 index 0000000000..39a0c7444c --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -0,0 +1,110 @@ +:host { + display: block; + width: 100%; + max-width: 320px; + min-width: 100%; + border-radius: 2px; + background: #fff; + + ::ng-deep { + .saturation-component { + height: 200px; + border-radius: 8px; + div.pointer { + border-width: 2px; + width: 16px; + height: 16px; + } + } + + indicator-component { + svg { + vertical-align: -70% !important; + } + } + + .hue-alpha-range { + alpha-component, hue-component { + height: 18px; + border: 1px solid rgba(0, 0, 0, 0.1); + .pointer { + height: 18px; + width: 18px; + background: none; + border: 2px solid #fff; + } + .gradient-color { + border-radius: 24px; + } + } + } + } +} + + + +.controls { + padding: 15px 0; +} + +.controls-row { + display: table; + width: 100%; +} + +.column { + display: table-cell; + vertical-align: middle; +} + +.hue-alpha .column:first-child { + width: 42px; + padding: 0 10px 0 0; +} + +indicator-component { + height: 48px; + width: 48px; + border-radius: 8px; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==') repeat; +} + +.hue-alpha-range { + hue-component, alpha-component { + border-radius: 24px; + } +} + +hue-component { + margin-bottom: 16px; +} + +color-presets-component { + border-top: 1px solid #d0d0d0; + padding: 12px; + + ::ng-deep .presets-row { + padding: 12px 0 0; + } +} + +.type-btn { + display: inline-block; + height: 20px; + width: 20px; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAYAAAAffCjxAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACewAAAnsB01CO3AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIASURBVEiJ7ZY9axRRFIafsxMStrLQJpAgpBFhi+C9w1YSo00I6RZ/g9vZpBf/QOr4GyRgkSKNSrAadsZqQGwCkuAWyRZJsySwvhZ7N/vhzrgbLH3Ld8597jlzz50zJokyxXH8DqDVar0qi6v8BbItqSGpEcfxdlmsFWXkvX8AfAVWg3UKPEnT9GKujMzsAFgZsVaCN1VTQd77XUnrgE1kv+6935268WRpzrnHZvYRWC7YvC3pRZZl3wozqtVqiyH9IgjAspkd1Gq1xUJQtVrdB9ZKIAOthdg/Qc65LUk7wNIMoCVJO865rYFhkqjX6/d7vV4GPJwBMqofURS5JEk6FYBer/eeYb/Mo9WwFnPOvQbeAvfuAAK4BN4sAJtAG/gJIElmNuiJyba3EGNmZiPeZuEVmVell/Y/6N+CzDn3AXhEOOo7Hv/3BeAz8IzQkMPnJbuPx1wC+yYJ7/0nYIP5S/0FHKdp+rwCEEXRS/rf5Hl1Gtb2M0iSpCOpCZzPATmX1EySpHMLAsiy7MjMDoHrGSDXZnaYZdnRwBh7J91utwmczAA6CbG3GgPleX4jqUH/a1CktqRGnuc3hSCAMB32gKspkCtgb3KCQMmkjeP4WNJThrNNZval1WptTIsv7JtQ4tmIdRa8qSoEpWl6YWZNoAN0zKxZNPehpLSBZv2t+Q0CJ9lLnARQLAAAAABJRU5ErkJggg==') no-repeat center; + background-size: 6px 12px; + + &:hover { + background-color: #eee; + } +} + +.type-column { + width: 25px; + text-align: right; +} + +.presentation { + padding: 12px 0 0; +} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts new file mode 100644 index 0000000000..e11d11f880 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -0,0 +1,106 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, SimpleChanges +} from '@angular/core'; +import { Color, ColorPickerControl } from '@iplab/ngx-color-picker'; +import { Subscription } from 'rxjs'; +export enum ColorType { + hex = 'hex', + hexa = 'hexa', + rgba = 'rgba', + rgb = 'rgb', + hsla = 'hsla', + hsl = 'hsl', + cmyk = 'cmyk' +} + +@Component({ + selector: `tb-color-picker`, + templateUrl: `./color-picker.component.html`, + styleUrls: [`./color-picker.component.scss`], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { + + public selectedPresentation = 0; + public presentations = [ColorType.hex, ColorType.hexa, ColorType.rgb, ColorType.rgba, ColorType.hsl, ColorType.hsla]; + + @Input() + public color: string; + + @Input() + public control: ColorPickerControl; + + @Output() + public colorChange: EventEmitter = new EventEmitter(false); + + private subscriptions: Array = []; + + constructor(private readonly cdr: ChangeDetectorRef) { + } + + public ngOnInit(): void { + if (!this.control) { + this.control = new ColorPickerControl(); + } + + if (this.color) { + this.control.setValueFrom(this.color); + } + + this.subscriptions.push( + this.control.valueChanges.subscribe((value) => { + this.cdr.markForCheck(); + this.colorChange.emit(this.getValueByType(value, this.control.initType)); + }) + ); + } + + changeColorFormat(event: Event) { + this.colorChange.emit(this.getValueByType(this.control.value, this.control.initType)); + } + + public ngOnDestroy(): void { + this.cdr.detach(); + this.subscriptions.forEach((subscription) => subscription.unsubscribe()); + this.subscriptions.length = 0; + } + + public ngOnChanges(changes: SimpleChanges): void { + if (this.color && this.control && this.getValueByType(this.control.value, this.control.initType) !== this.color) { + this.control.setValueFrom(this.color); + } + } + + public changePresentation(): void { + this.selectedPresentation = + this.selectedPresentation === this.presentations.length - 1 ? 0 : this.selectedPresentation + 1; + } + + getValueByType(color: Color, type: ColorType): string { + switch (type) { + case ColorType.hex: + return color.toHexString(); + case ColorType.hexa: + return color.toHexString(true); + case ColorType.rgb: + return color.toRgbString(); + case ColorType.rgba: + return color.toRgbaString(); + case ColorType.hsl: + return color.toHslString(); + case ColorType.hsla: + return color.toHslaString(); + default: + return color.toRgbaString(); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index a7d4835547..4ea0a5f8c5 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -17,12 +17,13 @@ -->
- - + + + + + + +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index e986ddc331..784da8da25 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -19,9 +19,19 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { + FormGroupDirective, + NgForm, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; +import { ColorPickerControl } from '@iplab/ngx-color-picker'; +import { ColorType } from '@shared/components/color-picker/color-picker.component'; +import * as tinycolor_ from 'tinycolor2'; export interface ColorPickerDialogData { color: string; @@ -40,12 +50,16 @@ export class ColorPickerDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ColorPickerDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, - public fb: UntypedFormBuilder) { + public fb: UntypedFormBuilder,) { super(store, router, dialogRef); } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 950532adab..10dbbef4aa 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -62,7 +62,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { ShareModule as ShareButtonsModule } from 'ngx-sharebuttons'; import { HotkeyModule } from 'angular2-hotkeys'; -import { ColorPickerModule } from 'ngx-color-picker'; +import { ColorPickerModule } from '@iplab/ngx-color-picker'; import { NgxHmCarouselModule } from 'ngx-hm-carousel'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { NospacePipe } from '@shared/pipe/nospace.pipe'; @@ -169,6 +169,8 @@ import { CustomDateAdapter } from '@shared/adapter/custom-datatime-adapter'; import { CustomPaginatorIntl } from '@shared/services/custom-paginator-intl'; import { TbScriptLangComponent } from '@shared/components/script-lang.component'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; +import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component'; +import { ColorPickerInputComponent } from '@shared/components/color-picker/color-picker-input.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -309,7 +311,9 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) BranchAutocompleteComponent, PhoneInputComponent, TbScriptLangComponent, - DateAgoPipe + DateAgoPipe, + ColorPickerComponent, + ColorPickerInputComponent ], imports: [ CommonModule, @@ -514,7 +518,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) BranchAutocompleteComponent, PhoneInputComponent, TbScriptLangComponent, - DateAgoPipe + DateAgoPipe, + ColorPickerComponent ] }) export class SharedModule { } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index b939f898f1..e4c90c98f3 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -1565,6 +1565,13 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@iplab/ngx-color-picker@^15.0.2": + version "15.0.2" + resolved "https://registry.yarnpkg.com/@iplab/ngx-color-picker/-/ngx-color-picker-15.0.2.tgz#856bf2571378e792e5e42b566ac1aa79a9262763" + integrity sha512-wum0Hg4Ky/6mhvzolEpFpjGckOjN8L2nkXvy3mWUVclFHP9MZqqgpJaxghtax7/tMdEWQnwQI3PYsNyKfF15ug== + dependencies: + tslib "^2.3.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" From 24d69651cb39fe6bedfdac64be89f8dc0b41dd8c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 23 Mar 2023 15:20:08 +0200 Subject: [PATCH 023/168] Fix space inside EncryptionUtil and fix failed-test, while doing validation --- .../java/org/thingsboard/server/common/msg/EncryptionUtil.java | 3 ++- .../server/dao/device/DeviceProfileServiceImpl.java | 2 +- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index 668dd28edc..e944a8dd92 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -39,7 +39,8 @@ public class EncryptionUtil { return input.replaceAll("\n", "") .replaceAll("\r", "") .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") - .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n"); + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n") + .trim(); } public static String pubkTrimNewLines(String input) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 6d19190199..c4ee12395f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -136,7 +136,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Fri, 24 Mar 2023 13:33:28 +0200 Subject: [PATCH 024/168] Device profile provision - fix for X509 provision strategy in provisionConfigurationFormGroup: - added resetFormControls() to reset redundant form controls; - allowCreateNewDevicesByX509Certificate added required validator; --- ...ofile-provision-configuration.component.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 465588fa24..caebc701d2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -90,7 +90,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu provisionDeviceKey: [{value: null, disabled: true}, Validators.required], certificateValue: [{value: null, disabled: true}, Validators.required], certificateRegExPattern: [{value: null, disabled: true}, Validators.required], - allowCreateNewDevicesByX509Certificate: [{value: null, disabled: true}] + allowCreateNewDevicesByX509Certificate: [{value: null, disabled: true}, Validators.required] }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { @@ -117,8 +117,6 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); } else { const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; if (!provisionDeviceSecret || !provisionDeviceSecret.length) { @@ -130,9 +128,6 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); } }); this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { @@ -178,6 +173,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu private updateModel(): void { let deviceProvisionConfiguration: DeviceProvisionConfiguration = null; + this.resetFormControls(this.provisionConfigurationFormGroup.value); if (this.provisionConfigurationFormGroup.valid) { deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue(); } @@ -194,4 +190,15 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu horizontalPosition: 'right' })); } + + private resetFormControls(value: DeviceProvisionConfiguration) { + if (value.type === DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES || value.type === DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES) { + this.provisionConfigurationFormGroup.get('certificateValue').reset({value: null, disabled: true}, {emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').reset({value: null, disabled: true}, {emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').reset({value: null, disabled: true}, {emitEvent: false}); + } else if (value.type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').reset({value: null, disabled: true}, {emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').reset({value: null, disabled: true}, {emitEvent: false}); + } + } } From be826687eb9bee9b29b969f4d7d366f3f3c89745 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 24 Mar 2023 14:42:01 +0200 Subject: [PATCH 025/168] Device profile provision strategy X509: updated hint locale hint-allow-create-new-devices --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e9e839ce8b..3dceb760ef 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1501,7 +1501,7 @@ "certificate-chain": "X509 Certificates Chain", "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN).

Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain.

Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known Certificate Authority (CA).", "allow-create-new-devices": "Create new devices", - "hint-allow-create-new-devices": "Hint: if selected new devices will be created with correct device credentials from client certificate.", + "hint-allow-create-new-devices": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", "certificate-value": "Certificate in PEM format", "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", From 1fee9a327302a0f20498267ed62715a22eb76908 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 29 Mar 2023 11:40:32 +0300 Subject: [PATCH 026/168] Revert "Add quarter and half year intervals to time window." --- .../src/app/shared/models/time/time.models.ts | 68 ------------------- .../assets/locale/locale.constant-en_US.json | 6 -- 2 files changed, 74 deletions(-) diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index ae093c71d2..6c04aef946 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -140,8 +140,6 @@ export enum QuickTimeInterval { PREVIOUS_WEEK = 'PREVIOUS_WEEK', PREVIOUS_WEEK_ISO = 'PREVIOUS_WEEK_ISO', PREVIOUS_MONTH = 'PREVIOUS_MONTH', - PREVIOUS_QUARTER = 'PREVIOUS_QUARTER', - PREVIOUS_HALF_YEAR = 'PREVIOUS_HALF_YEAR', PREVIOUS_YEAR = 'PREVIOUS_YEAR', CURRENT_HOUR = 'CURRENT_HOUR', CURRENT_DAY = 'CURRENT_DAY', @@ -152,10 +150,6 @@ export enum QuickTimeInterval { CURRENT_WEEK_ISO_SO_FAR = 'CURRENT_WEEK_ISO_SO_FAR', CURRENT_MONTH = 'CURRENT_MONTH', CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', - CURRENT_QUARTER = 'CURRENT_QUARTER', - CURRENT_QUARTER_SO_FAR = 'CURRENT_QUARTER_SO_FAR', - CURRENT_HALF_YEAR = 'CURRENT_HALF_YEAR', - CURRENT_HALF_YEAR_SO_FAR = 'CURRENT_HALF_YEAR_SO_FAR', CURRENT_YEAR = 'CURRENT_YEAR', CURRENT_YEAR_SO_FAR = 'CURRENT_YEAR_SO_FAR' } @@ -167,8 +161,6 @@ export const QuickTimeIntervalTranslationMap = new Map Date: Wed, 29 Mar 2023 11:40:41 +0300 Subject: [PATCH 027/168] Refactoring: fix conflict, add logs --- .../update/DefaultCacheCleanupService.java | 3 + .../transport/DefaultTransportApiService.java | 77 +++++++++++-------- .../src/main/resources/thingsboard.yml | 8 +- .../DefaultTransportApiServiceTest.java | 17 ++-- ...ertificateChainProvisionConfiguration.java | 7 +- .../mqtt/MqttSslHandlerProvider.java | 10 ++- .../server/common/transport/util/SslUtil.java | 1 + .../dao/device/DeviceProfileServiceImpl.java | 22 +++--- 8 files changed, 85 insertions(+), 60 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index bd840b2f52..1e54d8d48f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -82,6 +82,9 @@ public class DefaultCacheCleanupService implements CacheCleanupService { log.info("Clearing cache to upgrade from version 3.4.2 to 3.4.3 ..."); clearCacheByName("repositorySettings"); break; + case "3.4.4": + log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); + clearCacheByName("deviceProfiles"); default: //Do nothing, since cache cleanup is optional. } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index ba0c2e48ca..ca886cf49b 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -174,7 +174,7 @@ public class DefaultTransportApiService implements TransportApiService { result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasValidateOrCreateX509CertRequestMsg()) { TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateOrCreateX509CertRequestMsg(); - result = validateOrCreateDeviceX509Certificate(msg.getCertificateChain(), DeviceCredentialsType.X509_CERTIFICATE); + result = validateOrCreateDeviceX509Certificate(msg.getCertificateChain()); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -242,19 +242,12 @@ public class DefaultTransportApiService implements TransportApiService { } } - protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain) { List chain = convertX509CertificateChainToList(certChain); - String updateDeviceCertificateValue = chain.get(0); - String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); - String deviceCommonName = ""; - try { - deviceCommonName = SslUtil.parseCommonName(readCertFile(chain.get(0))); - } catch (Exception ignored) { - } for (String certificateValue : chain) { String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); - if (credentials != null && credentials.getCredentialsType() == credentialsType) { + if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); @@ -262,26 +255,31 @@ public class DefaultTransportApiService implements TransportApiService { X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration(); if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + } else { + log.warn("Device Profile provision configuration is not X509CertificateChainProvisionConfiguration"); } - String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, x509Configuration.getCertificateRegExPattern()); + String deviceName = extractDeviceNameFromCertificateCNByRegEx(chain.get(0), x509Configuration.getCertificateRegExPattern()); if (deviceName == null) { - log.error("Device name cannot be unmatched from CN!"); - return getEmptyTransportApiResponseFuture(); + log.warn("Device name has to be extract by regex from CN."); } Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); + String updateDeviceCertificateValue = chain.get(0); + String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); if (device != null) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { - deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); } else if (deviceCredentials == null) { - deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); } return getDeviceInfo(deviceCredentials); } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN && x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); - deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); return getDeviceInfo(deviceCredentials); + } else { + log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); } } } @@ -728,12 +726,16 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } - private String extractDeviceNameFromCNByRegEx(String commonName, String regex) { - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(commonName); - if (matcher.find()) { - return matcher.group(0); - } + private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + try { + String commonName = SslUtil.parseCommonName(readCertFile(x509Value)); + log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(commonName); + if (matcher.find()) { + return matcher.group(0); + } + } catch (Exception ignored) {} return null; } @@ -748,23 +750,26 @@ public class DefaultTransportApiService implements TransportApiService { return chain; } - private X509Certificate readCertFile(String fileContent) throws Exception { + private X509Certificate readCertFile(String fileContent) { X509Certificate certificate = null; - if (fileContent != null && !fileContent.trim().isEmpty()) { - fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replaceAll("\\s", ""); - byte[] decoded = Base64.decodeBase64(fileContent); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - try (InputStream inStream = new ByteArrayInputStream(decoded)) { - certificate = (X509Certificate) certFactory.generateCertificate(inStream); + try { + if (fileContent != null && !fileContent.trim().isEmpty()) { + fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(fileContent); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + try (InputStream inStream = new ByteArrayInputStream(decoded)) { + certificate = (X509Certificate) certFactory.generateCertificate(inStream); + } } - } + } catch (Exception ignored) {} return certificate; } private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, String certificateHash, DeviceCredentialsType credentialsType) { + log.trace("Updating device credentials [{}] with certificate id [{}]", deviceCredentials, certificateHash); deviceCredentials.setCredentialsId(certificateHash); deviceCredentials.setCredentialsValue(certificateValue); deviceCredentials.setCredentialsType(credentialsType); @@ -773,6 +778,7 @@ public class DefaultTransportApiService implements TransportApiService { private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, String certificateValue, String certificateHash, DeviceCredentialsType credentialsType) { + log.trace("Creating new deviceCredentials for device [{}] with certificate id [{}]", deviceId, certificateHash); DeviceCredentials createDevCredentials = new DeviceCredentials(); createDevCredentials.setDeviceId(deviceId); createDevCredentials.setCredentialsType(credentialsType); @@ -782,11 +788,14 @@ public class DefaultTransportApiService implements TransportApiService { } private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId, String deviceName, String type) { + log.trace("Creating new device for deviceProfile [{}] with device name [{}]", deviceProfileId, deviceName); Device device = new Device(); device.setTenantId(tenantId); device.setDeviceProfileId(deviceProfileId); device.setName(deviceName); device.setType(type); - return deviceService.saveDevice(device); + device = deviceService.saveDevice(device); + tbClusterService.onDeviceUpdated(device, null); + return device; } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d96e50ca7f..c7102f40a0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -735,7 +735,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:false}" + enabled: "${MQTT_SSL_ENABLED:true}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -749,11 +749,11 @@ transport: # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) - cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/server.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:/home/developer/server_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index 0309bec131..e98513826a 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -96,33 +96,34 @@ public class DefaultTransportApiServiceTest { private final String deviceCertificate = "-----BEGIN CERTIFICATE-----Device certificate value-----END CERTIFICATE-----"; private final String deviceProfileCertificate = "-----BEGIN CERTIFICATE-----Device profile certificate value-----END CERTIFICATE-----"; + private final String[] chain = new String[]{deviceCertificate, deviceProfileCertificate}; @Test public void validateExistingDeviceX509Certificate() { var device = createDevice(); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); - var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(deviceCertificate, DeviceCredentialsType.X509_CERTIFICATE); + service.validateOrCreateDeviceX509Certificate(chain[0]); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); } @Test public void updateExistingDeviceX509Certificate() { - var deviceProfile = createDeviceProfile(deviceProfileCertificate); + var deviceProfile = createDeviceProfile(chain[1]); when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); var device = createDevice(); when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); - var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + service.validateOrCreateDeviceX509Certificate(chain[1]); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); @@ -131,18 +132,18 @@ public class DefaultTransportApiServiceTest { @Test public void createDeviceByX509Provision() { - var deviceProfile = createDeviceProfile(deviceProfileCertificate); + var deviceProfile = createDeviceProfile(chain[1]); when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); var device = createDevice(); when(deviceService.saveDevice(any())).thenReturn(device); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); - var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + service.validateOrCreateDeviceX509Certificate(chain[1]); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java index 9257053b4f..34fee34ab3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java @@ -24,11 +24,16 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @NoArgsConstructor public class X509CertificateChainProvisionConfiguration implements DeviceProfileProvisionConfiguration { - private String provisionDeviceSecret; private String certificateValue; private String certificateRegExPattern; private boolean allowCreateNewDevicesByX509Certificate; + @Override + public String getProvisionDeviceSecret() { + // ignore device secret for this strategy + return null; + } + @Override public DeviceProfileProvisionType getType() { return DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 6025a44cb4..4b6e15292a 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -164,8 +164,14 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { - throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials."); + if (chain.length == 1) { + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + throw new CertificateException("Invalid Device Certificate"); + } + } else { + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + throw new CertificateException("Invalid Chain of X509 Certificates"); + } } } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index 51ec881490..da04d50f50 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -58,6 +58,7 @@ public class SslUtil { try { x500name = new JcaX509CertificateHolder(certificate).getSubject(); } catch (CertificateEncodingException e) { + log.warn("Cannot parse CN from device certificate"); throw new RuntimeException(e); } RDN cn = x500name.getRDNs(BCStyle.CN)[0]; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index c4ee12395f..3b4be7aef4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -135,12 +135,11 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Wed, 29 Mar 2023 13:26:14 +0300 Subject: [PATCH 028/168] Fix yml extra-config --- application/src/main/resources/thingsboard.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c7102f40a0..d96e50ca7f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -735,7 +735,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:true}" + enabled: "${MQTT_SSL_ENABLED:false}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -749,11 +749,11 @@ transport: # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) - cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/server.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:/home/developer/server_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) From e842c3da080cc8ff3af08ca1a6ff330a93f2d243 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 29 Mar 2023 18:53:36 +0300 Subject: [PATCH 029/168] Rewrite test using self-signed test x509 certificate --- .../transport/DefaultTransportApiService.java | 6 ++- .../DefaultTransportApiServiceTest.java | 52 ++++++++++++++++--- .../resources/mqtt/x509ChainProvision.pem | 28 ++++++++++ 3 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 application/src/test/resources/mqtt/x509ChainProvision.pem diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index ca886cf49b..f1ff95d3f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -252,15 +252,17 @@ public class DefaultTransportApiService implements TransportApiService { } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); if (deviceProfile != null) { - X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration(); + X509CertificateChainProvisionConfiguration x509Configuration; if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); } else { log.warn("Device Profile provision configuration is not X509CertificateChainProvisionConfiguration"); + return getEmptyTransportApiResponseFuture(); } String deviceName = extractDeviceNameFromCertificateCNByRegEx(chain.get(0), x509Configuration.getCertificateRegExPattern()); if (deviceName == null) { - log.warn("Device name has to be extract by regex from CN."); + log.warn("Cannot extract device name from device's CN using regex [{}]", x509Configuration.getCertificateRegExPattern()); + return getEmptyTransportApiResponseFuture(); } Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); String updateDeviceCertificateValue = chain.get(0); diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index e98513826a..a31d867242 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.transport; import com.google.common.util.concurrent.Futures; import lombok.extern.slf4j.Slf4j; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.mock.mockito.MockBean; @@ -49,7 +50,14 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; @@ -94,9 +102,20 @@ public class DefaultTransportApiServiceTest { @SpyBean DefaultTransportApiService service; - private final String deviceCertificate = "-----BEGIN CERTIFICATE-----Device certificate value-----END CERTIFICATE-----"; - private final String deviceProfileCertificate = "-----BEGIN CERTIFICATE-----Device profile certificate value-----END CERTIFICATE-----"; - private final String[] chain = new String[]{deviceCertificate, deviceProfileCertificate}; + private String certificateChain; + private String[] chain; + + @Before + public void setUp() { + String filePath = "src/test/resources/mqtt/x509ChainProvisionTest.pem"; + try { + certificateChain = Files.readString(Paths.get(filePath)); + certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain); + chain = fetchLeafCertificateFromChain(certificateChain); + } catch (IOException e) { + throw new RuntimeException(e); + } + } @Test public void validateExistingDeviceX509Certificate() { @@ -106,7 +125,7 @@ public class DefaultTransportApiServiceTest { var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(chain[0]); + service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); } @@ -123,7 +142,7 @@ public class DefaultTransportApiServiceTest { when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(chain[1]); + service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); @@ -143,7 +162,7 @@ public class DefaultTransportApiServiceTest { when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(chain[1]); + service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); @@ -164,7 +183,7 @@ public class DefaultTransportApiServiceTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); provision.setCertificateValue(certificateValue); - provision.setCertificateRegExPattern("^$"); + provision.setCertificateRegExPattern("([^@]+)"); provision.setAllowCreateNewDevicesByX509Certificate(true); deviceProfileData.setProvisionConfiguration(provision); deviceProfile.setProfileData(deviceProfileData); @@ -178,4 +197,23 @@ public class DefaultTransportApiServiceTest { device.setId(new DeviceId(UUID.randomUUID())); return device; } + + public static String certTrimNewLinesForChainInDeviceProfile(String input) { + return input.replaceAll("\n", "") + .replaceAll("\r", "") + .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n") + .trim(); + } + + private String[] fetchLeafCertificateFromChain(String value) { + List chain = new ArrayList<>(); + String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) { + chain.add(matcher.group(0)); + } + return chain.toArray(new String[0]); + } } diff --git a/application/src/test/resources/mqtt/x509ChainProvision.pem b/application/src/test/resources/mqtt/x509ChainProvision.pem new file mode 100644 index 0000000000..b2ec300f78 --- /dev/null +++ b/application/src/test/resources/mqtt/x509ChainProvision.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIICMTCCAdegAwIBAgIUI9dBuwN6pTtK6uZ03rkiCwV4wEYwCgYIKoZIzj0EAwIw +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlQ2VydGlmaWNhdGVAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MB4XDTIzMDMyOTE0NTYxN1oXDTI0MDMyODE0NTYxN1ow +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlQ2VydGlmaWNhdGVAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9Zo791qK +QiGNBm11r4ZGxh+w+ossZL3xc46ufq5QckQHP7zkD2XDAcmP5GvdkM1sBFN9AWaC +kQfNnWmfERsOOKNTMFEwHQYDVR0OBBYEFFFc5uyCyglQoZiKhzXzMcQ3BKORMB8G +A1UdIwQYMBaAFFFc5uyCyglQoZiKhzXzMcQ3BKORMA8GA1UdEwEB/wQFMAMBAf8w +CgYIKoZIzj0EAwIDSAAwRQIhANbA9CuhoOifZMMmqkpuld+65CR+ItKdXeRAhLMZ +uccuAiB0FSQB34zMutXrZj1g8Gl5OkE7YryFHbei1z0SveHR8g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICMTCCAdegAwIBAgIUUEKxS9hTz4l+oLUMF0LV6TC/gCIwCgYIKoZIzj0EAwIw +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlUHJvZmlsZUNlcnRAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MB4XDTIzMDMyOTE0NTczNloXDTI0MDMyODE0NTczNlow +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlUHJvZmlsZUNlcnRAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECMlWO72k +rDoUL9FQjUmSCetkhaEGJUfQkdSfkLSNa0GyAEIMbfmzI4zITeapunu4rGet3EMy +LydQzuQanBicp6NTMFEwHQYDVR0OBBYEFHpZ78tPnztNii4Da/yCw6mhEIL3MB8G +A1UdIwQYMBaAFHpZ78tPnztNii4Da/yCw6mhEIL3MA8GA1UdEwEB/wQFMAMBAf8w +CgYIKoZIzj0EAwIDSAAwRQIgJ7qyMFqNcwSYkH6o+UlQXzLWfwZbNjVk+aR7foAZ +NGsCIQDsd7v3WQIGHiArfZeDs1DLEDuV/2h6L+ZNoGNhEKL+1A== +-----END CERTIFICATE----- From 07e00e487c666ce000d1144a0c2b14d2e893ba1c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 30 Mar 2023 09:45:38 +0300 Subject: [PATCH 030/168] Rename file for x509 test chain --- .../mqtt/{x509ChainProvision.pem => x509ChainProvisionTest.pem} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename application/src/test/resources/mqtt/{x509ChainProvision.pem => x509ChainProvisionTest.pem} (100%) diff --git a/application/src/test/resources/mqtt/x509ChainProvision.pem b/application/src/test/resources/mqtt/x509ChainProvisionTest.pem similarity index 100% rename from application/src/test/resources/mqtt/x509ChainProvision.pem rename to application/src/test/resources/mqtt/x509ChainProvisionTest.pem From 99bc639cbe33eff4d26449584913a113c3326fe3 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 30 Mar 2023 16:03:39 +0300 Subject: [PATCH 031/168] Device profile -> Device provisioning -> X509 feature: - change tb-hints Info and Regex examples as tb-help-popup - fix locales for hint- --- ...ile-provision-configuration.component.html | 22 ++++++++++++------- .../en_US/device-profile/x509-chain-hint.md | 18 +++++++++++++++ .../x509-chain-regex-examples.md | 15 +++++++++++++ .../assets/locale/locale.constant-en_US.json | 7 +++--- 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md create mode 100644 ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 3b2b92ce98..6c5da8b531 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -35,14 +35,18 @@ -
- - {{ readMore ? (' ' + ('action.less' | translate)) : ('action.more' | translate) }} +
+ +
{{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} -
+
device-profile.provision-strategy-x509.certificate-value @@ -53,14 +57,16 @@ device-profile.provision-strategy-x509.cn-regex-variable +
{{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} - device-profile.provision-strategy-x509.hint-cn-regex-variable + device-profile.provision-strategy-x509.cn-regex-variable-hint
-
-
diff --git a/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md new file mode 100644 index 0000000000..f1aa10f921 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md @@ -0,0 +1,18 @@ +##### X509 Certificate Chain info + +X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. + +This strategy can: +* check for pre-provisioned devices +* update X.509 device credentials +* create new devices + +The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from *Common Name (CN)*. + +Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. + +The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). + +If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain. + +Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known *Certificate Authority (CA)*. diff --git a/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md new file mode 100644 index 0000000000..bb8a069473 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md @@ -0,0 +1,15 @@ +#### Examples of RegEx usage + +* **Pattern:** .* - matches any character (until line terminators) +
**CN sample:** DeviceName\nAdditionalInfo +
**Pattern matches:** DeviceName + +* **Pattern:** ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol) +
**CN sample:** DeviceName@AdditionalInfo +
**Pattern matches:** DeviceName + +* **Pattern:** [\w]*$ (equivalent to [a-zA-Z0-9_]\*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string +
**CN sample:** AdditionalInfo2110#DeviceName_01 +
**Pattern matches:** DeviceName_01 + +**Note:** Client will get error response in case regex is failed to match. diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ec9a151ede..de26259c00 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1505,15 +1505,14 @@ "provision-secret-copied-message": "Provision secret has been copied to clipboard", "provision-strategy-x509": { "certificate-chain": "X509 Certificates Chain", - "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN).

Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain.

Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known Certificate Authority (CA).", + "certificate-chain-hint": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication.", "allow-create-new-devices": "Create new devices", - "hint-allow-create-new-devices": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", + "allow-create-new-devices-hint": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", "certificate-value": "Certificate in PEM format", "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", "cn-regex-variable-required": "CN Regular Expression variable is required", - "hint-cn-regex-variable": "Required to fetch device name from device's X509 certificate's common name.", - "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceName_01
Note: Client will get error response in case regex is failed to match." + "cn-regex-variable-hint": "Required to fetch device name from device's X509 certificate's common name." }, "condition": "Condition", "condition-type": "Condition type", From 722b6f992cc5d61d979cee219ed93aedf8d1a71a Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 30 Mar 2023 16:37:47 +0300 Subject: [PATCH 032/168] Device profile -> Device provision stratetgy X509 minor changes in tb-help-popup html --- ...ce-profile-provision-configuration.component.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 6c5da8b531..afbaa924ac 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -37,11 +37,11 @@
-
+ trigger-text="{{ 'action.more' | translate }}">
{{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} @@ -57,11 +57,11 @@ device-profile.provision-strategy-x509.cn-regex-variable -
+ [tb-help-popup-style]="{maxWidth: '820px'}"> {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} From 98528f73eba0d81c44320223cd81483f8f1b30e6 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 30 Mar 2023 16:54:06 +0300 Subject: [PATCH 033/168] Remove 'Hint:' in locale allow-create-new-devices-hint --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index de26259c00..6f9653ca96 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1507,7 +1507,7 @@ "certificate-chain": "X509 Certificates Chain", "certificate-chain-hint": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication.", "allow-create-new-devices": "Create new devices", - "allow-create-new-devices-hint": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", + "allow-create-new-devices-hint": "If selected new devices will be created and client certificate will be used as device credentials.", "certificate-value": "Certificate in PEM format", "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", From 4022f4a8c36fe94773c2278d340a6b1a2fb77729 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 31 Mar 2023 11:40:04 +0300 Subject: [PATCH 034/168] UI: color-picker --- .../color-picker-input.component.html | 25 ++++++++++++++++--- .../color-picker-input.component.ts | 16 ++++++++++++ .../color-picker/color-picker.component.html | 19 ++++++++++++++ .../color-picker/color-picker.component.scss | 15 +++++++++++ .../color-picker/color-picker.component.ts | 16 ++++++++++++ .../dialog/color-picker-dialog.component.html | 2 +- 6 files changed, 88 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html index 564f1d64f2..c135369793 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html @@ -1,18 +1,35 @@ +
- + - + - +
- +
diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts index 22ce2c0849..445b65a969 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + import { ChangeDetectionStrategy, ChangeDetectorRef, diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html index dba4605736..4751d498f2 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html @@ -1,3 +1,20 @@ +
@@ -11,6 +28,7 @@
+
@@ -32,6 +50,7 @@ +
diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index 39a0c7444c..24fd427576 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ :host { display: block; width: 100%; diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts index e11d11f880..f2140e02a5 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + import { ChangeDetectionStrategy, ChangeDetectorRef, diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index 4ea0a5f8c5..8de970de65 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -16,7 +16,7 @@ --> -
+
From 5476b4a49d0955174c4de16f6c52c8b6b8c143f6 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 31 Mar 2023 12:26:20 +0300 Subject: [PATCH 035/168] Update PR template delete add version in templates --- pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pull_request_template.md b/pull_request_template.md index 21362429dc..d0bf8993f1 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -5,7 +5,7 @@ Put your PR description here instead of this sentence. ## General checklist - [ ] You have reviewed the guidelines [document](https://docs.google.com/document/d/1wqcOafLx5hth8SAg4dqV_LV3un3m5WYR8RdTJ4MbbUM/edit?usp=sharing). -- [ ] PR name contains fix version. For example, "[3.3.4] Hotfix of some UI component" or "[3.4] New Super Feature". +- [ ] [Labels](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels#about-labels) that classify your pull request have been added. - [ ] The [milestone](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/about-milestones) is specified and corresponds to fix version. - [ ] Description references specific [issue](https://github.com/thingsboard/thingsboard/issues). - [ ] Description contains human-readable scope of changes. From b7b1ed6150197046e2a7ebdd6a70603a9d681406 Mon Sep 17 00:00:00 2001 From: Seraphym-Tuhai Date: Thu, 6 Apr 2023 20:43:40 +0300 Subject: [PATCH 036/168] fix test --- .../msa/ui/tests/ruleChainsSmoke/RuleChainEditMenuTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/RuleChainEditMenuTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/RuleChainEditMenuTest.java index 5b380221b6..3c65bc8f0a 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/RuleChainEditMenuTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/RuleChainEditMenuTest.java @@ -151,12 +151,12 @@ public class RuleChainEditMenuTest extends AbstractDriverBaseTest { ruleChainsPage.editPencilBtn().click(); ruleChainsPage.debugCheckboxEdit().click(); ruleChainsPage.doneBtnEditView().click(); - boolean debugMode = Boolean.parseBoolean(ruleChainsPage.debugCheckboxView().getAttribute("aria-checked")); + boolean debugMode = ruleChainsPage.debugCheckboxView().getAttribute("class").contains("selected"); ruleChainsPage.editPencilBtn().click(); ruleChainsPage.debugCheckboxEdit().click(); ruleChainsPage.doneBtnEditView().click(); - Assert.assertFalse(Boolean.parseBoolean(ruleChainsPage.debugCheckboxView().getAttribute("aria-checked"))); - Assert.assertTrue(debugMode); + Assert.assertFalse(ruleChainsPage.debugCheckboxView().getAttribute("class").contains("selected"), "Debug mode disable"); + Assert.assertTrue(debugMode, "Debug mode enable"); } } \ No newline at end of file From bf3346d1ec8c5215d3a5ee9e008d0d945c0bad55 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 7 Apr 2023 11:51:45 +0300 Subject: [PATCH 037/168] Device Profile provision X.509 strategy update: save certificateValue as provisionDeviceSecret --- ...device-profile-provision-configuration.component.ts | 10 ++++++---- .../components/profile/device-profile.component.ts | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index caebc701d2..1ad497ddbb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -62,7 +62,6 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu deviceProvisionType = DeviceProvisionType; deviceProvisionTypes = Object.keys(DeviceProvisionType); deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap; - readMore: boolean; private requiredValue: boolean; get required(): boolean { @@ -143,16 +142,16 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } writeValue(value: DeviceProvisionConfiguration | null): void { - if (isDefinedAndNotNull(value)){ + if (isDefinedAndNotNull(value)) { this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false}); } else { this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED}); } } - setDisabledState(isDisabled: boolean){ + setDisabledState(isDisabled: boolean) { this.disabled = isDisabled; - if (this.disabled){ + if (this.disabled) { this.provisionConfigurationFormGroup.disable({emitEvent: false}); } else { if (this.provisionConfigurationFormGroup.get('type').value !== DeviceProvisionType.DISABLED) { @@ -176,6 +175,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.resetFormControls(this.provisionConfigurationFormGroup.value); if (this.provisionConfigurationFormGroup.valid) { deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue(); + if (deviceProvisionConfiguration.type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + deviceProvisionConfiguration.provisionDeviceSecret = deviceProvisionConfiguration.certificateValue; + } } this.propagateChange(deviceProvisionConfiguration); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 9a6d150ac1..db6539318d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -105,7 +105,6 @@ export class DeviceProfileComponent extends EntityComponent { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, - certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; @@ -189,7 +188,6 @@ export class DeviceProfileComponent extends EntityComponent { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, - certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; From d1a21900f0e713155d4f2cf29a161e8695826aca Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 7 Apr 2023 12:00:38 +0300 Subject: [PATCH 038/168] DeviceProfileProvisionConfigurationComponent writeValue set certificateValue as provisionDeviceSecret --- .../device-profile-provision-configuration.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 1ad497ddbb..54a9b98aa5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -143,6 +143,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu writeValue(value: DeviceProvisionConfiguration | null): void { if (isDefinedAndNotNull(value)) { + if (value.type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + value.certificateValue = value.provisionDeviceSecret; + } this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false}); } else { this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED}); From 55adb3d12a56a0a044263e4fc5eb486c4fa48c92 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Apr 2023 13:07:08 +0300 Subject: [PATCH 039/168] Refactor x509: add logic of rotating x509 credentials and creating new device to DeviceProvisionService --- .../main/data/upgrade/3.4.4/schema_update.sql | 10 -- .../device/DeviceProvisionServiceImpl.java | 76 ++++++----- .../transport/DefaultTransportApiService.java | 124 ++++++------------ .../src/main/resources/thingsboard.yml | 8 +- .../BaseDeviceProfileControllerTest.java | 11 +- .../DefaultTransportApiServiceTest.java | 65 ++++----- .../dao/device/DeviceProfileService.java | 2 +- .../server/common/data/DeviceProfile.java | 5 - ...ertificateChainProvisionConfiguration.java | 8 +- .../mqtt/MqttSslHandlerProvider.java | 9 +- .../server/common/transport/util/SslUtil.java | 21 +++ .../dao/device/DeviceProfileCacheKey.java | 14 +- .../server/dao/device/DeviceProfileDao.java | 1 - .../dao/device/DeviceProfileEvictEvent.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 40 +++--- .../server/dao/model/ModelConstants.java | 1 - .../dao/model/sql/DeviceProfileEntity.java | 5 - .../validator/DeviceProfileDataValidator.java | 24 ++-- .../sql/device/DeviceProfileRepository.java | 2 - .../dao/sql/device/JpaDeviceProfileDao.java | 5 - .../main/resources/sql/schema-entities.sql | 2 - 21 files changed, 188 insertions(+), 247 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 9bb85e3447..c9406cb38c 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -424,16 +424,6 @@ $$; -- ALARM FUNCTIONS END --- DEVICE PROFILE CERTIFICATE START - -ALTER TABLE device_profile - ADD COLUMN IF NOT EXISTS certificate_hash varchar, - DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, - ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); - --- DEVICE PROFILE CERTIFICATE END - - -- TTL DROP PARTITIONS FUNCTIONS UPDATE START DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 80b51f7ad5..dacddc8a1d 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; @@ -29,6 +28,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -36,15 +36,19 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceProfileDao; +import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; @@ -77,35 +81,23 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String PROVISIONED_STATE = "provisioned"; - @Autowired - TbClusterService clusterService; + private final TbClusterService clusterService; + private final DeviceProfileService deviceProfileService; + private final DeviceService deviceService; + private final DeviceCredentialsService deviceCredentialsService; + private final AttributesService attributesService; + private final AuditLogService auditLogService; + private final PartitionService partitionService; - @Autowired - DeviceDao deviceDao; - - @Autowired - DeviceProfileDao deviceProfileDao; - - @Autowired - DeviceService deviceService; - - @Autowired - DeviceCredentialsService deviceCredentialsService; - - @Autowired - AttributesService attributesService; - - @Autowired - DeviceStateService deviceStateService; - - @Autowired - AuditLogService auditLogService; - - @Autowired - PartitionService partitionService; - - public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) { + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceDao deviceDao, DeviceProfileDao deviceProfileDao, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, DeviceStateService deviceStateService, AuditLogService auditLogService, PartitionService partitionService) { ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); + this.clusterService = clusterService; + this.deviceProfileService = deviceProfileService; + this.deviceService = deviceService; + this.deviceCredentialsService = deviceCredentialsService; + this.attributesService = attributesService; + this.auditLogService = auditLogService; + this.partitionService = partitionService; } @Override @@ -124,14 +116,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } - DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); + DeviceProfile targetProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequestKey); if (targetProfile == null || targetProfile.getProfileData().getProvisionConfiguration() == null || targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() == null) { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } - Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); + Device targetDevice = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); switch (targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: @@ -155,6 +147,22 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } } break; + case X509_CERTIFICATE_CHAIN: + X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, + updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); + } + return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); + } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { + return createDevice(provisionRequest, targetProfile); + } else { + log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); + } } throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } @@ -209,6 +217,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } } + private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, + DeviceCredentialsType credentialsType) { + log.trace("Updating device credentials [{}] with certificate value [{}]", deviceCredentials, certificateValue); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsType(credentialsType); + return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); + } + private ListenableFuture> saveProvisionStateAttribute(Device device) { return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index f1ff95d3f2..dada1985f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -25,7 +25,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.ota.OtaPackageDataCache; @@ -34,7 +33,6 @@ import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; @@ -49,6 +47,7 @@ import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfigu import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; @@ -76,6 +75,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -106,10 +106,6 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -250,38 +246,13 @@ public class DefaultTransportApiService implements TransportApiService { if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return getDeviceInfo(credentials); } - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); if (deviceProfile != null) { - X509CertificateChainProvisionConfiguration x509Configuration; - if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { - x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); - } else { - log.warn("Device Profile provision configuration is not X509CertificateChainProvisionConfiguration"); - return getEmptyTransportApiResponseFuture(); - } - String deviceName = extractDeviceNameFromCertificateCNByRegEx(chain.get(0), x509Configuration.getCertificateRegExPattern()); - if (deviceName == null) { - log.warn("Cannot extract device name from device's CN using regex [{}]", x509Configuration.getCertificateRegExPattern()); - return getEmptyTransportApiResponseFuture(); - } - Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); - String updateDeviceCertificateValue = chain.get(0); - String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); - if (device != null) { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); - } else if (deviceCredentials == null) { - deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); - } - return getDeviceInfo(deviceCredentials); - } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN && x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { - Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); - deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); - return getDeviceInfo(deviceCredentials); - } else { - log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); + String updatedDeviceProvisionSecret = chain.get(0); + ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); + ProvisionResponse provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); + if (provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { + return getDeviceInfo(provisionResponse.getDeviceCredentials()); } } } @@ -728,9 +699,37 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } + private ProvisionRequest createProvisionRequest(DeviceProfile deviceProfile, String certificateValue) { + String deviceName = getDeviceName(deviceProfile, certificateValue); + + ProvisionDeviceProfileCredentials provisionDeviceProfileCredentials = new ProvisionDeviceProfileCredentials( + deviceProfile.getProvisionDeviceKey(), + deviceProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() + ); + ProvisionDeviceCredentialsData provisionDeviceCredentialsData = new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue); + + return new ProvisionRequest(deviceName, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); + } + + + private String getDeviceName(DeviceProfile deviceProfile, String certificateValue) { + X509CertificateChainProvisionConfiguration configuration = new X509CertificateChainProvisionConfiguration(); + String deviceName = null; + if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, configuration.getCertificateRegExPattern()); + if (deviceName == null) { + log.warn("Cannot extract device name from device's CN using regex [{}]", configuration.getCertificateRegExPattern()); + } + } else { + log.warn("Device Profile configuration: expected [{}], actual [{}]", configuration.getType(), deviceProfile.getProvisionType()); + } + return deviceName; + } + private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { try { - String commonName = SslUtil.parseCommonName(readCertFile(x509Value)); + String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commonName); @@ -751,53 +750,4 @@ public class DefaultTransportApiService implements TransportApiService { } return chain; } - - private X509Certificate readCertFile(String fileContent) { - X509Certificate certificate = null; - try { - if (fileContent != null && !fileContent.trim().isEmpty()) { - fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replaceAll("\\s", ""); - byte[] decoded = Base64.decodeBase64(fileContent); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - try (InputStream inStream = new ByteArrayInputStream(decoded)) { - certificate = (X509Certificate) certFactory.generateCertificate(inStream); - } - } - } catch (Exception ignored) {} - return certificate; - } - - private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, - String certificateHash, DeviceCredentialsType credentialsType) { - log.trace("Updating device credentials [{}] with certificate id [{}]", deviceCredentials, certificateHash); - deviceCredentials.setCredentialsId(certificateHash); - deviceCredentials.setCredentialsValue(certificateValue); - deviceCredentials.setCredentialsType(credentialsType); - return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); - } - - private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, String certificateValue, - String certificateHash, DeviceCredentialsType credentialsType) { - log.trace("Creating new deviceCredentials for device [{}] with certificate id [{}]", deviceId, certificateHash); - DeviceCredentials createDevCredentials = new DeviceCredentials(); - createDevCredentials.setDeviceId(deviceId); - createDevCredentials.setCredentialsType(credentialsType); - createDevCredentials.setCredentialsId(certificateHash); - createDevCredentials.setCredentialsValue(certificateValue); - return deviceCredentialsService.createDeviceCredentials(tenantId, createDevCredentials); - } - - private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId, String deviceName, String type) { - log.trace("Creating new device for deviceProfile [{}] with device name [{}]", deviceProfileId, deviceName); - Device device = new Device(); - device.setTenantId(tenantId); - device.setDeviceProfileId(deviceProfileId); - device.setName(deviceName); - device.setType(type); - device = deviceService.saveDevice(device); - tbClusterService.onDeviceUpdated(device, null); - return device; - } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d96e50ca7f..1a845b2188 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -735,7 +735,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:false}" + enabled: "${MQTT_SSL_ENABLED:true}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -749,11 +749,11 @@ transport: # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) - cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/x509/server.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:/home/developer/x509/server_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index e3017e4e19..e715e418ec 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -302,14 +302,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameCertificateHash() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); - deviceProfile.setCertificateHash("Certificate Hash"); - doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); + deviceProfile.setProvisionDeviceKey("Certificate hash"); + + doPost("/api/deviceProfile", deviceProfile) + .andExpect(status().isOk()); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); - deviceProfile2.setCertificateHash("Certificate Hash"); + deviceProfile2.setProvisionDeviceKey("Certificate hash"); Mockito.reset(tbClusterService, auditLogService); - String msgError = "Device profile with such certificate hash already exists"; + String msgError = "Device profile with such provision device key already exists!"; doPost("/api/deviceProfile", deviceProfile2) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index a31d867242..21a03c6167 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -40,6 +40,8 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -130,43 +132,42 @@ public class DefaultTransportApiServiceTest { } @Test - public void updateExistingDeviceX509Certificate() { + public void provisionDeviceX509Certificate() { var deviceProfile = createDeviceProfile(chain[1]); - when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); + when(deviceProfileService.findDeviceProfileByProvisionDeviceKey(any())).thenReturn(deviceProfile); var device = createDevice(); when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); - when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(null); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + var provisionResponse = createProvisionResponse(deviceCredentials); + when(deviceProvisionService.provisionDevice(any())).thenReturn(provisionResponse); + service.validateOrCreateDeviceX509Certificate(certificateChain); - verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); - verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); - verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); - verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + verify(deviceProfileService, times(1)).findDeviceProfileByProvisionDeviceKey(any()); + verify(deviceService, times(1)).findDeviceByIdAsync(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); + verify(deviceProvisionService, times(1)).provisionDevice(any()); } - @Test - public void createDeviceByX509Provision() { - var deviceProfile = createDeviceProfile(chain[1]); - when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); - - var device = createDevice(); - when(deviceService.saveDevice(any())).thenReturn(device); - when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); + private DeviceProfile createDeviceProfile(String certificateValue) { + X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); + provision.setProvisionDeviceSecret(certificateValue); + provision.setCertificateRegExPattern("([^@]+)"); + provision.setAllowCreateNewDevicesByX509Certificate(true); - var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); - when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); - when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setProvisionConfiguration(provision); - service.validateOrCreateDeviceX509Certificate(certificateChain); - verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); - verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); - verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); - verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setProvisionDeviceKey(EncryptionUtil.getSha3Hash(certificateValue)); + deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); + return deviceProfile; } private DeviceCredentials createDeviceCredentials(String certificateValue, DeviceId deviceId) { @@ -178,26 +179,16 @@ public class DefaultTransportApiServiceTest { return deviceCredentials; } - private DeviceProfile createDeviceProfile(String certificateValue) { - DeviceProfile deviceProfile = new DeviceProfile(); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); - provision.setCertificateValue(certificateValue); - provision.setCertificateRegExPattern("([^@]+)"); - provision.setAllowCreateNewDevicesByX509Certificate(true); - deviceProfileData.setProvisionConfiguration(provision); - deviceProfile.setProfileData(deviceProfileData); - deviceProfile.setCertificateHash(EncryptionUtil.getSha3Hash(certificateValue)); - deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); - return deviceProfile; - } - private Device createDevice() { Device device = new Device(); device.setId(new DeviceId(UUID.randomUUID())); return device; } + private ProvisionResponse createProvisionResponse(DeviceCredentials deviceCredentials) { + return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); + } + public static String certTrimNewLinesForChainInDeviceProfile(String input) { return input.replaceAll("\n", "") .replaceAll("\r", "") diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java index 90915c3ffe..e765cc5030 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java @@ -39,7 +39,7 @@ public interface DeviceProfileService extends EntityDaoService { PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); - DeviceProfile findDeviceProfileByCertificateHash(String credentialsId); + DeviceProfile findDeviceProfileByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 6d2c37e67d..7ff7d72023 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,10 +66,6 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 18, value = "CA certificate hash. ") - private String certificateHash; - - @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the root rule chain will be used to process those messages.") @@ -125,7 +121,6 @@ public class DeviceProfile extends SearchTextBased implements H this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); - this.certificateHash = deviceProfile.getCertificateHash(); this.externalId = deviceProfile.getExternalId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java index 34fee34ab3..d5edafca69 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java @@ -24,16 +24,10 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @NoArgsConstructor public class X509CertificateChainProvisionConfiguration implements DeviceProfileProvisionConfiguration { - private String certificateValue; + private String provisionDeviceSecret; private String certificateRegExPattern; private boolean allowCreateNewDevicesByX509Certificate; - @Override - public String getProvisionDeviceSecret() { - // ignore device secret for this strategy - return null; - } - @Override public DeviceProfileProvisionType getType() { return DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 4b6e15292a..f7a85425fe 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -164,12 +164,10 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (chain.length == 1) { - if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + if (chain.length == 1) { throw new CertificateException("Invalid Device Certificate"); - } - } else { - if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + } else { throw new CertificateException("Invalid Chain of X509 Certificates"); } } @@ -177,6 +175,5 @@ public class MqttSslHandlerProvider { log.error(e.getMessage(), e); } } - } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index da04d50f50..089718ec05 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.transport.util; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; @@ -24,8 +25,11 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.springframework.util.Base64Utils; import org.thingsboard.server.common.msg.EncryptionUtil; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; /** @@ -53,6 +57,23 @@ public class SslUtil { return stringBuilder.toString(); } + public static X509Certificate readCertFile(String fileContent) { + X509Certificate certificate = null; + try { + if (fileContent != null && !fileContent.trim().isEmpty()) { + fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(fileContent); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + try (InputStream inStream = new ByteArrayInputStream(decoded)) { + certificate = (X509Certificate) certFactory.generateCertificate(inStream); + } + } + } catch (Exception ignored) {} + return certificate; + } + public static String parseCommonName(X509Certificate certificate) { X500Name x500name; try { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index d15bde226b..715923f19b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -29,14 +29,14 @@ public class DeviceProfileCacheKey implements Serializable { private final TenantId tenantId; private final String name; private final DeviceProfileId deviceProfileId; - private final String certificateHash; + private final String provisionDeviceKey; private final boolean defaultProfile; - private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String certificateHash, boolean defaultProfile) { + private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String provisionDeviceKey, boolean defaultProfile) { this.tenantId = tenantId; this.name = name; this.deviceProfileId = deviceProfileId; - this.certificateHash = certificateHash; + this.provisionDeviceKey = provisionDeviceKey; this.defaultProfile = defaultProfile; } @@ -48,8 +48,8 @@ public class DeviceProfileCacheKey implements Serializable { return new DeviceProfileCacheKey(null, null, id, null, false); } - public static DeviceProfileCacheKey fromCertificateHash(String certificateHash) { - return new DeviceProfileCacheKey(null, null, null, certificateHash, false); + public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { + return new DeviceProfileCacheKey(null, null, null, provisionDeviceKey, false); } public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) { @@ -65,8 +65,8 @@ public class DeviceProfileCacheKey implements Serializable { return deviceProfileId.toString(); } else if (defaultProfile) { return tenantId.toString(); - } else if (certificateHash != null) { - return certificateHash; + } else if (provisionDeviceKey != null) { + return provisionDeviceKey; } return tenantId + "_" + name; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 206f139b8c..387042c627 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -46,5 +46,4 @@ public interface DeviceProfileDao extends Dao, ExportableEntityDa DeviceProfile findByName(TenantId tenantId, String profileName); - DeviceProfile findByCertificateHash(String certificateHash); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index 55b5f994c2..6470bfc584 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -27,6 +27,6 @@ public class DeviceProfileEvictEvent { private final String oldName; private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; - private final String certificateHash; + private final String provisionDeviceKey; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 3b4be7aef4..d601bc4ef2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -70,7 +70,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findById(tenantId, deviceProfileId.getId()), true); } @@ -120,7 +120,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByName(tenantId, profileName), true); } @@ -128,7 +128,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByCertificateHash(certificateHash), true); + public DeviceProfile findDeviceProfileByProvisionDeviceKey(String provisionDeviceKey) { + log.trace("Executing findDeviceProfileIdByCredentialsId credentialId [{}]", provisionDeviceKey); + validateString(provisionDeviceKey, INCORRECT_PROVISION_DEVICE_KEY + provisionDeviceKey); + return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromProvisionDeviceKey(provisionDeviceKey), + () -> deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), true); } @Override @@ -290,7 +290,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService impl @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) private UUID externalId; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY) - private String certificateHash; - public DeviceProfileEntity() { super(); } @@ -129,7 +126,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.image = deviceProfile.getImage(); this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); - this.certificateHash = deviceProfile.getCertificateHash(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -192,7 +188,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId)); } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); - deviceProfile.setCertificateHash(certificateHash); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 8f4f89e2a8..dfd8d7b832 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -132,14 +132,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Fri, 7 Apr 2023 13:54:48 +0300 Subject: [PATCH 040/168] Refactoring: remove extra comments, remove spaces etc --- .../main/data/upgrade/3.4.4/schema_update.sql | 1 - .../device/DeviceProvisionServiceImpl.java | 2 +- application/src/main/resources/thingsboard.yml | 8 ++++---- .../dao/device/DeviceProfileCacheKey.java | 18 +++++++++--------- .../server/dao/device/DeviceProfileDao.java | 1 - .../validator/DeviceProfileDataValidator.java | 8 -------- 6 files changed, 14 insertions(+), 24 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 2781c9119d..2eb6a0d6cc 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -424,7 +424,6 @@ $$; -- ALARM FUNCTIONS END - -- TTL DROP PARTITIONS FUNCTIONS UPDATE START DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index dacddc8a1d..d4bfe9c625 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -89,7 +89,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private final AuditLogService auditLogService; private final PartitionService partitionService; - public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceDao deviceDao, DeviceProfileDao deviceProfileDao, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, DeviceStateService deviceStateService, AuditLogService auditLogService, PartitionService partitionService) { + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, AuditLogService auditLogService, PartitionService partitionService) { ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); this.clusterService = clusterService; this.deviceProfileService = deviceProfileService; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ef42460108..f745e03ffc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -741,7 +741,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:true}" + enabled: "${MQTT_SSL_ENABLED:false}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -755,11 +755,11 @@ transport: # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) - cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/x509/server.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:/home/developer/x509/server_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index 715923f19b..6e55b0b6ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -29,31 +29,31 @@ public class DeviceProfileCacheKey implements Serializable { private final TenantId tenantId; private final String name; private final DeviceProfileId deviceProfileId; - private final String provisionDeviceKey; private final boolean defaultProfile; + private final String provisionDeviceKey; - private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String provisionDeviceKey, boolean defaultProfile) { + private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, boolean defaultProfile, String provisionDeviceKey) { this.tenantId = tenantId; this.name = name; this.deviceProfileId = deviceProfileId; - this.provisionDeviceKey = provisionDeviceKey; this.defaultProfile = defaultProfile; + this.provisionDeviceKey = provisionDeviceKey; } public static DeviceProfileCacheKey fromName(TenantId tenantId, String name) { - return new DeviceProfileCacheKey(tenantId, name, null, null, false); + return new DeviceProfileCacheKey(tenantId, name, null, false, null); } public static DeviceProfileCacheKey fromId(DeviceProfileId id) { - return new DeviceProfileCacheKey(null, null, id, null, false); + return new DeviceProfileCacheKey(null, null, id, false, null); } - public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { - return new DeviceProfileCacheKey(null, null, null, provisionDeviceKey, false); + public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) { + return new DeviceProfileCacheKey(tenantId, null, null, true, null); } - public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) { - return new DeviceProfileCacheKey(tenantId, null, null, null, true); + public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { + return new DeviceProfileCacheKey(null, null, null, false, provisionDeviceKey); } /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 387042c627..c221002413 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -45,5 +45,4 @@ public interface DeviceProfileDao extends Dao, ExportableEntityDa DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findByName(TenantId tenantId, String profileName); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index dfd8d7b832..073b685167 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -136,10 +136,6 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Fri, 7 Apr 2023 17:49:56 +0300 Subject: [PATCH 041/168] Add more detailed logs and move extract logic by regex to deviceProvisionService --- .../device/DeviceProvisionServiceImpl.java | 39 +++++++++++++-- .../transport/DefaultTransportApiService.java | 49 ++++++------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d4bfe9c625..33b2af55fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -46,8 +45,6 @@ import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; @@ -63,12 +60,13 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Service @@ -102,6 +100,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @Override public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { + fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(provisionRequest); String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) { @@ -160,7 +159,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { return createDevice(provisionRequest, targetProfile); } else { - log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); + log.warn("Device with name {} doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration", provisionRequest.getDeviceName()); throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } } @@ -273,4 +272,34 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); } + + private void fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(ProvisionRequest provisionRequest) { + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequest.getCredentials().getProvisionDeviceKey()); + X509CertificateChainProvisionConfiguration configuration; + if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + String certificateRegEx = configuration.getCertificateRegExPattern(); + String deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, certificateRegEx); + if (deviceName == null) { + log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]",certificateRegEx, certificateValue); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + provisionRequest.setDeviceName(deviceName); + } + } + + private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + try { + String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); + log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(commonName); + if (matcher.find()) { + return matcher.group(0); + } + return null; + } catch (Exception ignored) {} + return null; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index dada1985f3..031564aecf 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -47,9 +47,7 @@ import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfigu import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; -import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -67,7 +65,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; @@ -118,6 +115,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID; @@ -247,13 +245,17 @@ public class DefaultTransportApiService implements TransportApiService { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); - if (deviceProfile != null) { + if (deviceProfile != null && deviceProfile.getProvisionType() == X509_CERTIFICATE_CHAIN) { String updatedDeviceProvisionSecret = chain.get(0); ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); - ProvisionResponse provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); - if (provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { + ProvisionResponse provisionResponse = provisionDeviceRequestAndGetResponse(provisionRequest); + if (provisionResponse != null && provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { return getDeviceInfo(provisionResponse.getDeviceCredentials()); + } else { + return getEmptyTransportApiResponseFuture(); } + } else if (deviceProfile != null) { + log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getName(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); } } return getEmptyTransportApiResponseFuture(); @@ -700,44 +702,21 @@ public class DefaultTransportApiService implements TransportApiService { } private ProvisionRequest createProvisionRequest(DeviceProfile deviceProfile, String certificateValue) { - String deviceName = getDeviceName(deviceProfile, certificateValue); - ProvisionDeviceProfileCredentials provisionDeviceProfileCredentials = new ProvisionDeviceProfileCredentials( deviceProfile.getProvisionDeviceKey(), deviceProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() ); ProvisionDeviceCredentialsData provisionDeviceCredentialsData = new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue); - return new ProvisionRequest(deviceName, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); + return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); } - - private String getDeviceName(DeviceProfile deviceProfile, String certificateValue) { - X509CertificateChainProvisionConfiguration configuration = new X509CertificateChainProvisionConfiguration(); - String deviceName = null; - if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { - configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); - deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, configuration.getCertificateRegExPattern()); - if (deviceName == null) { - log.warn("Cannot extract device name from device's CN using regex [{}]", configuration.getCertificateRegExPattern()); - } - } else { - log.warn("Device Profile configuration: expected [{}], actual [{}]", configuration.getType(), deviceProfile.getProvisionType()); - } - return deviceName; - } - - private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + private ProvisionResponse provisionDeviceRequestAndGetResponse(ProvisionRequest provisionRequest) { + ProvisionResponse provisionResponse = null; try { - String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); - log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(commonName); - if (matcher.find()) { - return matcher.group(0); - } - } catch (Exception ignored) {} - return null; + provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); + } catch (ProvisionFailedException ignored) {} + return provisionResponse; } private List convertX509CertificateChainToList(String certificateChain) { From df18155c815366cfa8c3ca5c9f7b75abc771e39b Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Apr 2023 18:02:25 +0300 Subject: [PATCH 042/168] Refactoring --- .../server/service/transport/DefaultTransportApiService.java | 2 +- .../server/transport/mqtt/MqttSslHandlerProvider.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 031564aecf..8e1a05945f 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -255,7 +255,7 @@ public class DefaultTransportApiService implements TransportApiService { return getEmptyTransportApiResponseFuture(); } } else if (deviceProfile != null) { - log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getName(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); + log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getId(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); } } return getEmptyTransportApiResponseFuture(); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index f7a85425fe..1cc4f90915 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -165,6 +165,7 @@ public class MqttSslHandlerProvider { }); latch.await(10, TimeUnit.SECONDS); if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + log.debug("Failed to find credentials for device certificate chain: {}", chain); if (chain.length == 1) { throw new CertificateException("Invalid Device Certificate"); } else { From b0d35372aad702cf38f0e4a52cc30dbfdfd390bc Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Apr 2023 18:49:04 +0300 Subject: [PATCH 043/168] Replace arguments in comparison to avoid null pointer --- .../server/service/transport/DefaultTransportApiService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 8e1a05945f..16aa2d97d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -249,7 +249,7 @@ public class DefaultTransportApiService implements TransportApiService { String updatedDeviceProvisionSecret = chain.get(0); ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); ProvisionResponse provisionResponse = provisionDeviceRequestAndGetResponse(provisionRequest); - if (provisionResponse != null && provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { + if (provisionResponse != null && ProvisionResponseStatus.SUCCESS.equals(provisionResponse.getResponseStatus())) { return getDeviceInfo(provisionResponse.getDeviceCredentials()); } else { return getEmptyTransportApiResponseFuture(); From e71f6bcc267c16b7ca9ddf8daa88f04689ccb5f7 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 10 Apr 2023 09:35:32 +0300 Subject: [PATCH 044/168] Refactoring x509 provisioning, fix tests --- .../service/device/DeviceProvisionServiceImpl.java | 6 ++---- .../service/transport/DefaultTransportApiService.java | 9 +++++---- .../server/dao/device/DeviceProfileServiceImpl.java | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 33b2af55fe..f9f24e43b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -275,9 +275,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(ProvisionRequest provisionRequest) { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequest.getCredentials().getProvisionDeviceKey()); - X509CertificateChainProvisionConfiguration configuration; - if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { - configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + if (deviceProfile != null && deviceProfile.getProfileData() != null && deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); String certificateRegEx = configuration.getCertificateRegExPattern(); String deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, certificateRegEx); @@ -298,7 +297,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { if (matcher.find()) { return matcher.group(0); } - return null; } catch (Exception ignored) {} return null; } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 16aa2d97d3..226f716225 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -712,11 +712,12 @@ public class DefaultTransportApiService implements TransportApiService { } private ProvisionResponse provisionDeviceRequestAndGetResponse(ProvisionRequest provisionRequest) { - ProvisionResponse provisionResponse = null; try { - provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); - } catch (ProvisionFailedException ignored) {} - return provisionResponse; + return deviceProvisionService.provisionDevice(provisionRequest); + } catch (ProvisionFailedException e) { + log.error(e.getMessage()); + } + return null; } private List convertX509CertificateChainToList(String certificateChain) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index d601bc4ef2..6883ec567a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -221,7 +221,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), true); From 4a216070d5bcb4b3cc002cb34075c129755cb845 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 10 Apr 2023 10:45:50 +0300 Subject: [PATCH 045/168] Replace converting from string chain to list from method to streams --- .../transport/DefaultTransportApiService.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 226f716225..27f20bb5fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -103,7 +103,6 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -111,7 +110,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -130,6 +128,8 @@ public class DefaultTransportApiService implements TransportApiService { private static final ObjectMapper mapper = new ObjectMapper(); + private static final Pattern X509_CERTIFICATE_TRIM_CHAIN_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"); + private final TbDeviceProfileCache deviceProfileCache; private final TbTenantProfileCache tenantProfileCache; private final TbApiUsageStateService apiUsageStateService; @@ -236,8 +236,9 @@ public class DefaultTransportApiService implements TransportApiService { } } - protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain) { - List chain = convertX509CertificateChainToList(certChain); + protected ListenableFuture validateOrCreateDeviceX509Certificate(String certificateChain) { + List chain = X509_CERTIFICATE_TRIM_CHAIN_PATTERN.matcher(certificateChain).results().map(match -> + EncryptionUtil.certTrimNewLines(match.group())).collect(Collectors.toList()); for (String certificateValue : chain) { String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); @@ -720,14 +721,4 @@ public class DefaultTransportApiService implements TransportApiService { return null; } - private List convertX509CertificateChainToList(String certificateChain) { - List chain = new ArrayList<>(); - String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(certificateChain); - while (matcher.find()) { - chain.add(EncryptionUtil.certTrimNewLines(matcher.group())); - } - return chain; - } } From 29c3eddc8a2d5ce4050d6e7e11a364966e5df5f1 Mon Sep 17 00:00:00 2001 From: Seraphym-Tuhai Date: Mon, 10 Apr 2023 12:33:45 +0300 Subject: [PATCH 046/168] Merge with branch 'develop/3.5' of github.com:thingsboard/thingsboard --- .github/release.yml | 2 +- application/pom.xml | 28 +- application/src/main/conf/logback.xml | 2 +- application/src/main/conf/thingsboard.conf | 2 +- .../data/json/demo/dashboards/gateways.json | 4 +- .../json/demo/dashboards/thermostats.json | 10 + .../docker/instructions.md | 107 + .../docker/localhost_warning.md | 3 + .../rule_chains/edge_root_rule_chain.json | 77 +- .../system/widget_bundles/alarm_widgets.json | 2 +- .../widget_bundles/control_widgets.json | 10 +- .../widget_bundles/entity_admin_widgets.json | 4 +- .../system/widget_bundles/input_widgets.json | 32 +- .../data/json/system/widget_bundles/maps.json | 18 +- .../src/main/data/lwm2m-registry/0.xml | 405 + .../src/main/data/lwm2m-registry/1.xml | 360 + .../src/main/data/lwm2m-registry/10.xml | 217 + .../src/main/data/lwm2m-registry/10241.xml | 81 + .../src/main/data/lwm2m-registry/10242.xml | 659 ++ .../src/main/data/lwm2m-registry/10243.xml | 263 + .../src/main/data/lwm2m-registry/10244.xml | 303 + .../src/main/data/lwm2m-registry/10245.xml | 217 + .../src/main/data/lwm2m-registry/10246.xml | 108 + .../src/main/data/lwm2m-registry/10247.xml | 98 + .../src/main/data/lwm2m-registry/10248.xml | 78 + .../src/main/data/lwm2m-registry/10249.xml | 138 + .../src/main/data/lwm2m-registry/10250.xml | 70 + .../src/main/data/lwm2m-registry/10251.xml | 96 + .../src/main/data/lwm2m-registry/10252.xml | 229 + .../src/main/data/lwm2m-registry/10253.xml | 72 + .../src/main/data/lwm2m-registry/10254.xml | 119 + .../src/main/data/lwm2m-registry/10255.xml | 165 + .../src/main/data/lwm2m-registry/10256.xml | 117 + .../src/main/data/lwm2m-registry/10257.xml | 275 + .../src/main/data/lwm2m-registry/10258.xml | 89 + .../src/main/data/lwm2m-registry/10259.xml | 115 + .../src/main/data/lwm2m-registry/10260.xml | 95 + .../src/main/data/lwm2m-registry/10262.xml | 89 + .../src/main/data/lwm2m-registry/10263.xml | 92 + .../src/main/data/lwm2m-registry/10264.xml | 119 + .../src/main/data/lwm2m-registry/10265.xml | 138 + .../src/main/data/lwm2m-registry/10266.xml | 246 + .../src/main/data/lwm2m-registry/10267.xml | 246 + .../src/main/data/lwm2m-registry/10268.xml | 246 + .../src/main/data/lwm2m-registry/10269.xml | 246 + .../src/main/data/lwm2m-registry/10270.xml | 246 + .../src/main/data/lwm2m-registry/10271.xml | 246 + .../src/main/data/lwm2m-registry/10272.xml | 232 + .../src/main/data/lwm2m-registry/10273.xml | 232 + .../src/main/data/lwm2m-registry/10274.xml | 232 + .../src/main/data/lwm2m-registry/10275.xml | 232 + .../src/main/data/lwm2m-registry/10276.xml | 232 + .../src/main/data/lwm2m-registry/10277.xml | 232 + .../src/main/data/lwm2m-registry/10278.xml | 232 + .../src/main/data/lwm2m-registry/10279.xml | 232 + .../src/main/data/lwm2m-registry/10280.xml | 232 + .../src/main/data/lwm2m-registry/10281.xml | 232 + .../src/main/data/lwm2m-registry/10282.xml | 232 + .../src/main/data/lwm2m-registry/10283.xml | 232 + .../src/main/data/lwm2m-registry/10284.xml | 232 + .../src/main/data/lwm2m-registry/10286.xml | 48 + .../src/main/data/lwm2m-registry/10290.xml | 198 + .../src/main/data/lwm2m-registry/10291.xml | 208 + .../src/main/data/lwm2m-registry/10292.xml | 208 + .../src/main/data/lwm2m-registry/10299.xml | 113 + .../src/main/data/lwm2m-registry/10300.xml | 142 + .../src/main/data/lwm2m-registry/10308.xml | 145 + .../src/main/data/lwm2m-registry/10309.xml | 115 + .../src/main/data/lwm2m-registry/10311.xml | 192 + .../src/main/data/lwm2m-registry/10313.xml | 253 + .../src/main/data/lwm2m-registry/10314.xml | 106 + .../src/main/data/lwm2m-registry/10315.xml | 129 + .../src/main/data/lwm2m-registry/10316.xml | 233 + .../src/main/data/lwm2m-registry/10318.xml | 189 + .../src/main/data/lwm2m-registry/10319.xml | 130 + .../src/main/data/lwm2m-registry/10320.xml | 141 + .../src/main/data/lwm2m-registry/10322.xml | 87 + .../src/main/data/lwm2m-registry/10323.xml | 90 + .../src/main/data/lwm2m-registry/10324.xml | 77 + .../src/main/data/lwm2m-registry/10326.xml | 722 ++ .../src/main/data/lwm2m-registry/10327.xml | 68 + .../src/main/data/lwm2m-registry/10328.xml | 80 + .../src/main/data/lwm2m-registry/10329.xml | 433 + .../src/main/data/lwm2m-registry/10330.xml | 120 + .../src/main/data/lwm2m-registry/10331.xml | 221 + .../src/main/data/lwm2m-registry/10332.xml | 69 + .../src/main/data/lwm2m-registry/10333.xml | 88 + .../src/main/data/lwm2m-registry/10334.xml | 129 + .../src/main/data/lwm2m-registry/10335.xml | 97 + .../src/main/data/lwm2m-registry/10336.xml | 84 + .../src/main/data/lwm2m-registry/10337.xml | 118 + .../src/main/data/lwm2m-registry/10338.xml | 124 + .../src/main/data/lwm2m-registry/10339.xml | 114 + .../src/main/data/lwm2m-registry/10340.xml | 218 + .../src/main/data/lwm2m-registry/10341.xml | 84 + .../src/main/data/lwm2m-registry/10342.xml | 84 + .../src/main/data/lwm2m-registry/10343.xml | 96 + .../src/main/data/lwm2m-registry/10344.xml | 96 + .../src/main/data/lwm2m-registry/10345.xml | 96 + .../src/main/data/lwm2m-registry/10346.xml | 96 + .../src/main/data/lwm2m-registry/10347.xml | 154 + .../src/main/data/lwm2m-registry/10348.xml | 96 + .../src/main/data/lwm2m-registry/10349.xml | 84 + .../src/main/data/lwm2m-registry/10350.xml | 116 + .../src/main/data/lwm2m-registry/10351.xml | 79 + .../src/main/data/lwm2m-registry/10352.xml | 114 + .../src/main/data/lwm2m-registry/10353.xml | 87 + .../src/main/data/lwm2m-registry/10354.xml | 217 + .../src/main/data/lwm2m-registry/10355.xml | 95 + .../src/main/data/lwm2m-registry/10356.xml | 203 + .../src/main/data/lwm2m-registry/10357.xml | 95 + .../src/main/data/lwm2m-registry/10358.xml | 70 + .../src/main/data/lwm2m-registry/10359.xml | 78 + .../src/main/data/lwm2m-registry/10360.xml | 70 + .../src/main/data/lwm2m-registry/10361.xml | 92 + .../src/main/data/lwm2m-registry/10362.xml | 91 + .../src/main/data/lwm2m-registry/10363.xml | 57 + .../src/main/data/lwm2m-registry/10364.xml | 57 + .../src/main/data/lwm2m-registry/10365.xml | 57 + .../src/main/data/lwm2m-registry/10366.xml | 59 + .../src/main/data/lwm2m-registry/10368.xml | 96 + .../src/main/data/lwm2m-registry/10369.xml | 84 + .../src/main/data/lwm2m-registry/10371.xml | 85 + .../src/main/data/lwm2m-registry/10374.xml | 161 + .../src/main/data/lwm2m-registry/10375.xml | 148 + .../src/main/data/lwm2m-registry/10376.xml | 211 + .../src/main/data/lwm2m-registry/10377.xml | 251 + .../src/main/data/lwm2m-registry/10378.xml | 91 + .../src/main/data/lwm2m-registry/11.xml | 408 + .../src/main/data/lwm2m-registry/12.xml | 551 + .../src/main/data/lwm2m-registry/13.xml | 209 + .../src/main/data/lwm2m-registry/14.xml | 139 + .../src/main/data/lwm2m-registry/15.xml | 175 + .../src/main/data/lwm2m-registry/16.xml | 127 + .../src/main/data/lwm2m-registry/18830.xml | 173 + .../src/main/data/lwm2m-registry/18831.xml | 120 + .../src/main/data/lwm2m-registry/19.xml | 144 + .../src/main/data/lwm2m-registry/2.xml | 123 + .../src/main/data/lwm2m-registry/20.xml | 142 + .../src/main/data/lwm2m-registry/2048.xml | 90 + .../src/main/data/lwm2m-registry/2049.xml | 59 + .../src/main/data/lwm2m-registry/2050.xml | 64 + .../src/main/data/lwm2m-registry/2051.xml | 99 + .../src/main/data/lwm2m-registry/2052.xml | 111 + .../src/main/data/lwm2m-registry/2053.xml | 165 + .../src/main/data/lwm2m-registry/2054.xml | 69 + .../src/main/data/lwm2m-registry/2055.xml | 100 + .../src/main/data/lwm2m-registry/2056.xml | 77 + .../src/main/data/lwm2m-registry/2057.xml | 92 + .../src/main/data/lwm2m-registry/21.xml | 132 + .../src/main/data/lwm2m-registry/22.xml | 157 + .../src/main/data/lwm2m-registry/23.xml | 100 + .../src/main/data/lwm2m-registry/24.xml | 163 + .../src/main/data/lwm2m-registry/25.xml | 104 + .../src/main/data/lwm2m-registry/26.xml | 93 + .../src/main/data/lwm2m-registry/27.xml | 322 + .../src/main/data/lwm2m-registry/28.xml | 150 + .../src/main/data/lwm2m-registry/3.xml | 331 + .../src/main/data/lwm2m-registry/3200.xml | 148 + .../src/main/data/lwm2m-registry/3201.xml | 98 + .../src/main/data/lwm2m-registry/3202.xml | 168 + .../src/main/data/lwm2m-registry/3203.xml | 108 + .../src/main/data/lwm2m-registry/3300.xml | 178 + .../src/main/data/lwm2m-registry/3301.xml | 168 + .../src/main/data/lwm2m-registry/3302.xml | 158 + .../src/main/data/lwm2m-registry/3303.xml | 168 + .../src/main/data/lwm2m-registry/3304.xml | 168 + .../src/main/data/lwm2m-registry/3305.xml | 278 + .../src/main/data/lwm2m-registry/3306.xml | 118 + .../src/main/data/lwm2m-registry/3308.xml | 108 + .../src/main/data/lwm2m-registry/3310.xml | 138 + .../src/main/data/lwm2m-registry/3311.xml | 127 + .../src/main/data/lwm2m-registry/3312.xml | 127 + .../src/main/data/lwm2m-registry/3313.xml | 157 + .../src/main/data/lwm2m-registry/3314.xml | 147 + .../src/main/data/lwm2m-registry/3315.xml | 168 + .../src/main/data/lwm2m-registry/3316.xml | 179 + .../src/main/data/lwm2m-registry/3317.xml | 179 + .../src/main/data/lwm2m-registry/3318.xml | 179 + .../src/main/data/lwm2m-registry/3319.xml | 179 + .../src/main/data/lwm2m-registry/3320.xml | 179 + .../src/main/data/lwm2m-registry/3321.xml | 179 + .../src/main/data/lwm2m-registry/3322.xml | 179 + .../src/main/data/lwm2m-registry/3323.xml | 179 + .../src/main/data/lwm2m-registry/3324.xml | 179 + .../src/main/data/lwm2m-registry/3325.xml | 179 + .../src/main/data/lwm2m-registry/3326.xml | 179 + .../src/main/data/lwm2m-registry/3327.xml | 179 + .../src/main/data/lwm2m-registry/3328.xml | 179 + .../src/main/data/lwm2m-registry/3329.xml | 179 + .../src/main/data/lwm2m-registry/3330.xml | 179 + .../src/main/data/lwm2m-registry/3331.xml | 129 + .../src/main/data/lwm2m-registry/3332.xml | 139 + .../src/main/data/lwm2m-registry/3333.xml | 98 + .../src/main/data/lwm2m-registry/3334.xml | 229 + .../src/main/data/lwm2m-registry/3335.xml | 119 + .../src/main/data/lwm2m-registry/3336.xml | 149 + .../src/main/data/lwm2m-registry/3337.xml | 159 + .../src/main/data/lwm2m-registry/3338.xml | 119 + .../src/main/data/lwm2m-registry/3339.xml | 98 + .../src/main/data/lwm2m-registry/3340.xml | 159 + .../src/main/data/lwm2m-registry/3341.xml | 139 + .../src/main/data/lwm2m-registry/3342.xml | 118 + .../src/main/data/lwm2m-registry/3343.xml | 89 + .../src/main/data/lwm2m-registry/3344.xml | 99 + .../src/main/data/lwm2m-registry/3345.xml | 109 + .../src/main/data/lwm2m-registry/3346.xml | 179 + .../src/main/data/lwm2m-registry/3347.xml | 99 + .../src/main/data/lwm2m-registry/3348.xml | 89 + .../src/main/data/lwm2m-registry/3349.xml | 108 + .../src/main/data/lwm2m-registry/3350.xml | 128 + .../src/main/data/lwm2m-registry/3351.xml | 131 + .../src/main/data/lwm2m-registry/3352.xml | 124 + .../src/main/data/lwm2m-registry/3353.xml | 123 + .../src/main/data/lwm2m-registry/3354.xml | 134 + .../src/main/data/lwm2m-registry/3355.xml | 156 + .../src/main/data/lwm2m-registry/3356.xml | 120 + .../src/main/data/lwm2m-registry/3357.xml | 131 + .../src/main/data/lwm2m-registry/3358.xml | 106 + .../src/main/data/lwm2m-registry/3359.xml | 105 + .../src/main/data/lwm2m-registry/3360.xml | 128 + .../src/main/data/lwm2m-registry/3361.xml | 130 + .../src/main/data/lwm2m-registry/3362.xml | 97 + .../src/main/data/lwm2m-registry/3363.xml | 100 + .../src/main/data/lwm2m-registry/3364.xml | 97 + .../src/main/data/lwm2m-registry/3365.xml | 123 + .../src/main/data/lwm2m-registry/3366.xml | 176 + .../src/main/data/lwm2m-registry/3367.xml | 138 + .../src/main/data/lwm2m-registry/3368.xml | 100 + .../src/main/data/lwm2m-registry/3369.xml | 105 + .../src/main/data/lwm2m-registry/3370.xml | 141 + .../src/main/data/lwm2m-registry/3371.xml | 141 + .../src/main/data/lwm2m-registry/3372.xml | 115 + .../src/main/data/lwm2m-registry/3373.xml | 115 + .../src/main/data/lwm2m-registry/3374.xml | 132 + .../src/main/data/lwm2m-registry/3375.xml | 150 + .../src/main/data/lwm2m-registry/3376.xml | 97 + .../src/main/data/lwm2m-registry/3377.xml | 168 + .../src/main/data/lwm2m-registry/3378.xml | 115 + .../src/main/data/lwm2m-registry/3379.xml | 123 + .../src/main/data/lwm2m-registry/3380.xml | 204 + .../src/main/data/lwm2m-registry/3381.xml | 110 + .../src/main/data/lwm2m-registry/3382.xml | 108 + .../src/main/data/lwm2m-registry/3383.xml | 99 + .../src/main/data/lwm2m-registry/3384.xml | 112 + .../src/main/data/lwm2m-registry/3385.xml | 110 + .../src/main/data/lwm2m-registry/3386.xml | 105 + .../src/main/data/lwm2m-registry/3387.xml | 224 + .../src/main/data/lwm2m-registry/3388.xml | 178 + .../src/main/data/lwm2m-registry/3389.xml | 108 + .../src/main/data/lwm2m-registry/3390.xml | 298 + .../src/main/data/lwm2m-registry/3391.xml | 258 + .../src/main/data/lwm2m-registry/3392.xml | 198 + .../src/main/data/lwm2m-registry/3393.xml | 268 + .../src/main/data/lwm2m-registry/3394.xml | 177 + .../src/main/data/lwm2m-registry/3395.xml | 182 + .../src/main/data/lwm2m-registry/3396.xml | 270 + .../src/main/data/lwm2m-registry/3397.xml | 302 + .../src/main/data/lwm2m-registry/3398.xml | 107 + .../src/main/data/lwm2m-registry/3399.xml | 238 + .../src/main/data/lwm2m-registry/3400.xml | 88 + .../src/main/data/lwm2m-registry/3401.xml | 120 + .../src/main/data/lwm2m-registry/3402.xml | 88 + .../src/main/data/lwm2m-registry/3403.xml | 118 + .../src/main/data/lwm2m-registry/3404.xml | 118 + .../src/main/data/lwm2m-registry/3405.xml | 98 + .../src/main/data/lwm2m-registry/3406.xml | 88 + .../src/main/data/lwm2m-registry/3407.xml | 98 + .../src/main/data/lwm2m-registry/3408.xml | 79 + .../src/main/data/lwm2m-registry/3410.xml | 148 + .../src/main/data/lwm2m-registry/3411.xml | 168 + .../src/main/data/lwm2m-registry/3412.xml | 288 + .../src/main/data/lwm2m-registry/3413.xml | 108 + .../src/main/data/lwm2m-registry/3414.xml | 108 + .../src/main/data/lwm2m-registry/3415.xml | 98 + .../src/main/data/lwm2m-registry/3416.xml | 518 + .../src/main/data/lwm2m-registry/3417.xml | 188 + .../src/main/data/lwm2m-registry/3418.xml | 348 + .../src/main/data/lwm2m-registry/3419.xml | 78 + .../src/main/data/lwm2m-registry/3420.xml | 58 + .../src/main/data/lwm2m-registry/3421.xml | 618 ++ .../src/main/data/lwm2m-registry/3423.xml | 158 + .../src/main/data/lwm2m-registry/3424.xml | 178 + .../src/main/data/lwm2m-registry/3425.xml | 108 + .../src/main/data/lwm2m-registry/3426.xml | 188 + .../src/main/data/lwm2m-registry/3427.xml | 138 + .../src/main/data/lwm2m-registry/3428.xml | 268 + .../src/main/data/lwm2m-registry/3429.xml | 108 + .../src/main/data/lwm2m-registry/3430.xml | 371 + .../src/main/data/lwm2m-registry/3431.xml | 98 + .../src/main/data/lwm2m-registry/3432.xml | 218 + .../src/main/data/lwm2m-registry/3433.xml | 128 + .../src/main/data/lwm2m-registry/3434.xml | 128 + .../src/main/data/lwm2m-registry/3435.xml | 178 + .../src/main/data/lwm2m-registry/3436.xml | 98 + .../src/main/data/lwm2m-registry/3437.xml | 118 + .../src/main/data/lwm2m-registry/3438.xml | 78 + .../src/main/data/lwm2m-registry/3439.xml | 78 + .../src/main/data/lwm2m-registry/3441.xml | 262 + .../src/main/data/lwm2m-registry/3442.xml | 332 + .../src/main/data/lwm2m-registry/4.xml | 258 + .../src/main/data/lwm2m-registry/5.xml | 255 + .../src/main/data/lwm2m-registry/500.xml | 189 + .../src/main/data/lwm2m-registry/501.xml | 163 + .../src/main/data/lwm2m-registry/502.xml | 214 + .../src/main/data/lwm2m-registry/503.xml | 434 + .../src/main/data/lwm2m-registry/504.xml | 341 + .../src/main/data/lwm2m-registry/505.xml | 262 + .../src/main/data/lwm2m-registry/507.xml | 115 + .../src/main/data/lwm2m-registry/508.xml | 115 + .../src/main/data/lwm2m-registry/509.xml | 138 + .../src/main/data/lwm2m-registry/510.xml | 103 + .../src/main/data/lwm2m-registry/6.xml | 143 + .../src/main/data/lwm2m-registry/7.xml | 164 + .../src/main/data/lwm2m-registry/8.xml | 141 + .../src/main/data/lwm2m-registry/9.xml | 321 + .../main/data/upgrade/1.3.0/schema_update.cql | 2 +- .../main/data/upgrade/1.3.1/schema_update.sql | 2 +- .../main/data/upgrade/1.4.0/schema_update.cql | 2 +- .../main/data/upgrade/1.4.0/schema_update.sql | 2 +- .../main/data/upgrade/2.0.0/schema_update.cql | 2 +- .../main/data/upgrade/2.0.0/schema_update.sql | 2 +- .../main/data/upgrade/2.1.1/schema_update.cql | 2 +- .../main/data/upgrade/2.1.1/schema_update.sql | 2 +- .../main/data/upgrade/2.1.2/schema_update.cql | 2 +- .../main/data/upgrade/2.1.2/schema_update.sql | 2 +- .../main/data/upgrade/2.2.0/schema_update.sql | 2 +- .../main/data/upgrade/2.3.1/schema_update.sql | 2 +- .../main/data/upgrade/2.4.0/schema_update.sql | 2 +- .../main/data/upgrade/2.4.2/schema_update.sql | 2 +- .../schema_update_psql_drop_partitions.sql | 2 +- .../upgrade/2.4.3/schema_update_psql_ts.sql | 2 +- .../2.4.3/schema_update_timescale_ts.sql | 2 +- .../data/upgrade/2.4.3/schema_update_ttl.sql | 2 +- .../data/upgrade/3.0.1/schema_ts_latest.sql | 2 +- .../upgrade/3.0.1/schema_update_to_uuid.sql | 2 +- .../main/data/upgrade/3.1.0/schema_update.sql | 2 +- .../upgrade/3.1.1/schema_update_after.sql | 2 +- .../upgrade/3.1.1/schema_update_before.sql | 2 +- .../main/data/upgrade/3.2.1/schema_update.sql | 2 +- .../data/upgrade/3.2.1/schema_update_ttl.sql | 2 +- .../main/data/upgrade/3.2.2/schema_update.sql | 2 +- .../upgrade/3.2.2/schema_update_event.sql | 2 +- .../data/upgrade/3.2.2/schema_update_ttl.sql | 2 +- .../main/data/upgrade/3.3.2/schema_update.sql | 2 +- .../3.3.2/schema_update_lwm2m_bootstrap.sql | 2 +- .../3.3.3/schema_event_ttl_procedure.sql | 2 +- .../main/data/upgrade/3.3.3/schema_update.sql | 2 +- .../main/data/upgrade/3.3.4/schema_update.sql | 2 +- .../main/data/upgrade/3.4.0/schema_update.sql | 2 +- .../main/data/upgrade/3.4.1/schema_update.sql | 2 +- .../upgrade/3.4.1/schema_update_after.sql | 2 +- .../upgrade/3.4.1/schema_update_before.sql | 2 +- .../main/data/upgrade/3.4.4/schema_update.sql | 612 ++ .../server/ThingsboardInstallApplication.java | 2 +- .../server/ThingsboardServerApplication.java | 2 +- .../server/actors/ActorSystemContext.java | 47 +- .../actors/TbEntityTypeActorIdPredicate.java | 2 +- .../server/actors/app/AppActor.java | 2 +- .../server/actors/app/AppInitMsg.java | 2 +- .../server/actors/device/DeviceActor.java | 2 +- .../actors/device/DeviceActorCreator.java | 2 +- .../device/DeviceActorMessageProcessor.java | 4 +- .../server/actors/device/SessionInfo.java | 2 +- .../actors/device/SessionInfoMetaData.java | 2 +- .../actors/device/SessionTimeoutCheckMsg.java | 2 +- .../device/ToDeviceRpcRequestMetadata.java | 2 +- .../device/ToServerRpcRequestMetadata.java | 2 +- .../actors/ruleChain/DefaultTbContext.java | 56 +- .../actors/ruleChain/RuleChainActor.java | 15 +- .../RuleChainActorMessageProcessor.java | 4 +- .../actors/ruleChain/RuleChainInputMsg.java | 2 +- .../ruleChain/RuleChainManagerActor.java | 2 +- .../actors/ruleChain/RuleChainOutputMsg.java | 2 +- .../ruleChain/RuleChainToRuleChainMsg.java | 2 +- .../ruleChain/RuleChainToRuleNodeMsg.java | 2 +- .../ruleChain/RuleEngineComponentActor.java | 68 + .../actors/ruleChain/RuleNodeActor.java | 15 +- .../RuleNodeActorMessageProcessor.java | 2 +- .../server/actors/ruleChain/RuleNodeCtx.java | 2 +- .../actors/ruleChain/RuleNodeRelation.java | 2 +- .../RuleNodeToRuleChainTellNextMsg.java | 2 +- .../actors/ruleChain/RuleNodeToSelfMsg.java | 2 +- .../ruleChain/TbToRuleChainActorMsg.java | 2 +- .../ruleChain/TbToRuleNodeActorMsg.java | 2 +- .../server/actors/service/ActorService.java | 2 +- .../server/actors/service/ComponentActor.java | 9 +- .../actors/service/ContextAwareActor.java | 2 +- .../actors/service/ContextBasedCreator.java | 2 +- .../actors/service/DefaultActorService.java | 2 +- .../AbstractContextAwareMsgProcessor.java | 2 +- .../actors/shared/ActorTerminationMsg.java | 2 +- .../actors/shared/ComponentMsgProcessor.java | 2 +- .../server/actors/stats/StatsActor.java | 2 +- .../server/actors/stats/StatsPersistMsg.java | 2 +- .../server/actors/stats/StatsPersistTick.java | 2 +- .../actors/tenant/DebugTbRateLimits.java | 2 +- .../server/actors/tenant/TenantActor.java | 10 +- ...tomOAuth2AuthorizationRequestResolver.java | 2 +- .../server/config/MvcCorsProperties.java | 2 +- .../config/RateLimitProcessingFilter.java | 2 +- .../config/SchedulingConfiguration.java | 2 +- .../server/config/SwaggerConfiguration.java | 4 +- .../ThingsboardMessageConfiguration.java | 2 +- .../ThingsboardSecurityConfiguration.java | 14 +- .../thingsboard/server/config/WebConfig.java | 2 +- .../server/config/WebSocketConfiguration.java | 19 +- .../controller/AbstractRpcController.java | 4 +- .../server/controller/AdminController.java | 265 +- .../controller/AlarmCommentController.java | 124 + .../server/controller/AlarmController.java | 128 +- .../server/controller/AssetController.java | 231 +- .../controller/AssetProfileController.java | 40 +- .../server/controller/AuditLogController.java | 58 +- .../server/controller/AuthController.java | 185 +- .../controller/AutoCommitController.java | 2 +- .../server/controller/BaseController.java | 392 +- .../ComponentDescriptorController.java | 26 +- .../controller/ControllerConstants.java | 7 +- .../server/controller/CustomerController.java | 60 +- .../controller/DashboardController.java | 242 +- .../server/controller/DeviceController.java | 227 +- .../controller/DeviceProfileController.java | 52 +- .../server/controller/EdgeController.java | 257 +- .../controller/EdgeEventController.java | 16 +- .../EntitiesVersionControlController.java | 2 +- .../controller/EntityQueryController.java | 54 +- .../controller/EntityRelationController.java | 85 +- .../controller/EntityViewController.java | 193 +- .../server/controller/EventController.java | 12 +- .../controller/HttpValidationCallback.java | 2 +- .../server/controller/Lwm2mController.java | 8 +- .../controller/NotificationController.java | 323 + .../NotificationRuleController.java | 103 + .../NotificationTargetController.java | 199 + .../NotificationTemplateController.java | 156 + .../OAuth2ConfigTemplateController.java | 28 +- .../server/controller/OAuth2Controller.java | 58 +- .../controller/OtaPackageController.java | 64 +- .../server/controller/QueueController.java | 2 +- .../server/controller/RpcV1Controller.java | 2 +- .../server/controller/RpcV2Controller.java | 104 +- .../controller/RuleChainController.java | 267 +- .../controller/SystemInfoController.java | 2 +- .../controller/TbResourceController.java | 68 +- .../server/controller/TbUrlConstants.java | 2 +- .../controller/TelemetryController.java | 134 +- .../server/controller/TenantController.java | 40 +- .../controller/TenantProfileController.java | 99 +- .../TwoFactorAuthConfigController.java | 2 +- .../controller/TwoFactorAuthController.java | 2 +- .../controller/UiSettingsController.java | 2 +- .../controller/UsageInfoController.java | 45 + .../server/controller/UserController.java | 407 +- .../controller/WidgetTypeController.java | 147 +- .../controller/WidgetsBundleController.java | 40 +- .../controller/plugin/TbWebSocketHandler.java | 91 +- .../controller/plugin/TbWebSocketMsg.java | 2 +- .../controller/plugin/TbWebSocketMsgType.java | 2 +- .../controller/plugin/TbWebSocketPingMsg.java | 2 +- .../controller/plugin/TbWebSocketTextMsg.java | 2 +- .../exception/AccessDeniedException.java | 4 +- .../exception/EntityNotFoundException.java | 4 +- .../exception/InternalErrorException.java | 4 +- .../exception/InvalidParametersException.java | 4 +- ...ThingsboardCredentialsExpiredResponse.java | 2 +- .../exception/ThingsboardErrorResponse.java | 2 +- .../ThingsboardErrorResponseHandler.java | 2 +- .../exception/ToErrorResponseEntity.java | 4 +- .../exception/UnauthorizedException.java | 4 +- .../exception/UncheckedApiException.java | 4 +- .../ThingsboardInstallConfiguration.java | 2 +- .../install/ThingsboardInstallException.java | 2 +- .../install/ThingsboardInstallService.java | 28 +- .../service/action/EntityActionService.java | 27 +- .../service/apiusage/BaseApiUsageState.java | 2 +- .../apiusage/CustomerApiUsageState.java | 2 +- .../DefaultTbApiUsageStateService.java | 62 +- .../apiusage/TbApiUsageStateService.java | 2 +- .../service/apiusage/TenantApiUsageState.java | 2 +- .../{ => limits}/DefaultRateLimitService.java | 30 +- .../service/apiusage/limits/LimitedApi.java | 36 + .../{ => limits}/RateLimitService.java | 8 +- .../service/asset/AssetBulkImportService.java | 2 +- .../AnnotationComponentDiscoveryService.java | 2 +- .../component/ComponentDiscoveryService.java | 2 +- .../device/ClaimDevicesServiceImpl.java | 2 +- .../device/DeviceBulkImportService.java | 2 +- .../device/DeviceProvisionServiceImpl.java | 2 +- .../edge/DefaultEdgeNotificationService.java | 34 +- .../service/edge/EdgeBulkImportService.java | 2 +- .../service/edge/EdgeContextComponent.java | 38 +- .../service/edge/EdgeNotificationService.java | 2 +- .../DefaultEdgeInstallService.java | 94 + .../edge/instructions/EdgeInstallService.java | 28 + .../edge/rpc/EdgeEventStorageSettings.java | 2 +- .../service/edge/rpc/EdgeGrpcService.java | 2 +- .../service/edge/rpc/EdgeGrpcSession.java | 22 +- .../service/edge/rpc/EdgeRpcService.java | 2 +- .../service/edge/rpc/EdgeSessionState.java | 2 +- .../service/edge/rpc/EdgeSyncCursor.java | 11 +- .../AdminSettingsMsgConstructor.java | 2 +- .../rpc/constructor/AlarmMsgConstructor.java | 2 +- .../rpc/constructor/AssetMsgConstructor.java | 2 +- .../AssetProfileMsgConstructor.java | 6 +- .../constructor/CustomerMsgConstructor.java | 2 +- .../constructor/DashboardMsgConstructor.java | 2 +- .../rpc/constructor/DeviceMsgConstructor.java | 9 +- .../DeviceProfileMsgConstructor.java | 14 +- .../rpc/constructor/EdgeMsgConstructor.java | 2 +- .../constructor/EntityDataMsgConstructor.java | 2 +- .../constructor/EntityViewMsgConstructor.java | 2 +- .../constructor/OtaPackageMsgConstructor.java | 2 +- .../rpc/constructor/QueueMsgConstructor.java | 2 +- .../constructor/RelationMsgConstructor.java | 2 +- .../constructor/RuleChainMsgConstructor.java | 2 +- .../rpc/constructor/UserMsgConstructor.java | 2 +- .../constructor/WidgetTypeMsgConstructor.java | 2 +- .../WidgetsBundleMsgConstructor.java | 2 +- .../AbstractRuleChainMetadataConstructor.java | 2 +- .../rule/RuleChainMetadataConstructor.java | 2 +- .../RuleChainMetadataConstructorFactory.java | 2 +- .../RuleChainMetadataConstructorV330.java | 2 +- .../RuleChainMetadataConstructorV340.java | 2 +- .../fetch/AdminSettingsEdgeEventFetcher.java | 2 +- .../fetch/AssetProfilesEdgeEventFetcher.java | 2 +- .../rpc/fetch/AssetsEdgeEventFetcher.java | 2 +- .../fetch/BasePageableEdgeEventFetcher.java | 2 +- .../rpc/fetch/BaseUsersEdgeEventFetcher.java | 2 +- .../BaseWidgetsBundlesEdgeEventFetcher.java | 2 +- .../rpc/fetch/CustomerEdgeEventFetcher.java | 7 +- .../fetch/CustomerUsersEdgeEventFetcher.java | 2 +- .../rpc/fetch/DashboardsEdgeEventFetcher.java | 2 +- .../fetch/DeviceProfilesEdgeEventFetcher.java | 2 +- .../rpc/fetch/DevicesEdgeEventFetcher.java | 2 +- .../edge/rpc/fetch/EdgeEventFetcher.java | 2 +- .../fetch/EntityViewsEdgeEventFetcher.java | 2 +- .../rpc/fetch/GeneralEdgeEventFetcher.java | 2 +- .../fetch/OtaPackagesEdgeEventFetcher.java | 2 +- .../rpc/fetch/QueuesEdgeEventFetcher.java | 2 +- .../rpc/fetch/RuleChainsEdgeEventFetcher.java | 2 +- .../SystemWidgetsBundlesEdgeEventFetcher.java | 2 +- .../TenantAdminUsersEdgeEventFetcher.java | 2 +- .../TenantWidgetsBundlesEdgeEventFetcher.java | 2 +- .../rpc/processor/AlarmEdgeProcessor.java | 195 - .../edge/rpc/processor/BaseEdgeProcessor.java | 48 +- .../rpc/processor/RelationEdgeProcessor.java | 153 - .../processor/alarm/AlarmEdgeProcessor.java | 102 + .../processor/alarm/BaseAlarmProcessor.java | 137 + .../{ => asset}/AssetEdgeProcessor.java | 5 +- .../AssetProfileEdgeProcessor.java | 5 +- .../{ => customer}/CustomerEdgeProcessor.java | 5 +- .../DashboardEdgeProcessor.java | 5 +- .../processor/device/BaseDeviceProcessor.java | 134 + .../{ => device}/DeviceEdgeProcessor.java | 269 +- .../DeviceProfileEdgeProcessor.java | 5 +- .../processor/{ => edge}/EdgeProcessor.java | 5 +- .../EntityViewEdgeProcessor.java | 5 +- .../{ => ota}/OtaPackageEdgeProcessor.java | 5 +- .../{ => queue}/QueueEdgeProcessor.java | 5 +- .../relation/BaseRelationProcessor.java | 76 + .../relation/RelationEdgeProcessor.java | 82 + .../{ => rule}/RuleChainEdgeProcessor.java | 5 +- .../AdminSettingsEdgeProcessor.java | 5 +- .../BaseTelemetryProcessor.java} | 168 +- .../telemetry/TelemetryEdgeProcessor.java | 48 + .../{ => user}/UserEdgeProcessor.java | 5 +- .../WidgetBundleEdgeProcessor.java | 5 +- .../{ => widget}/WidgetTypeEdgeProcessor.java | 5 +- .../rpc/sync/DefaultEdgeRequestsService.java | 150 +- .../edge/rpc/sync/EdgeRequestsService.java | 2 +- .../entitiy/AbstractTbEntityService.java | 9 +- .../DefaultTbNotificationEntityService.java | 23 +- .../entitiy/SimpleTbEntityService.java | 2 +- .../entitiy/TbNotificationEntityService.java | 12 +- .../alarm/DefaultTbAlarmCommentService.java | 71 + .../entitiy/alarm/DefaultTbAlarmService.java | 193 +- .../entitiy/alarm/TbAlarmCommentService.java | 27 + .../service/entitiy/alarm/TbAlarmService.java | 17 +- .../entitiy/asset/DefaultTbAssetService.java | 8 +- .../service/entitiy/asset/TbAssetService.java | 2 +- .../profile/DefaultTbAssetProfileService.java | 2 +- .../asset/profile/TbAssetProfileService.java | 2 +- .../customer/DefaultTbCustomerService.java | 2 +- .../entitiy/customer/TbCustomerService.java | 2 +- .../dashboard/DefaultTbDashboardService.java | 18 +- .../entitiy/dashboard/TbDashboardService.java | 2 +- .../device/DefaultTbDeviceService.java | 8 +- .../entitiy/device/TbDeviceService.java | 2 +- .../DefaultTbDeviceProfileService.java | 2 +- .../profile/TbDeviceProfileService.java | 2 +- .../entitiy/edge/DefaultTbEdgeService.java | 6 +- .../service/entitiy/edge/TbEdgeService.java | 2 +- .../DefaultTbEntityRelationService.java | 2 +- .../relation/TbEntityRelationService.java | 2 +- .../DefaultTbEntityViewService.java | 22 +- .../entityview/TbEntityViewService.java | 5 +- .../ota/DefaultTbOtaPackageService.java | 2 +- .../entitiy/ota/TbOtaPackageService.java | 2 +- .../entitiy/queue/DefaultTbQueueService.java | 2 +- .../service/entitiy/queue/TbQueueService.java | 2 +- .../tenant/DefaultTbTenantService.java | 2 +- .../entitiy/tenant/TbTenantService.java | 2 +- .../DefaultTbTenantProfileService.java | 2 +- .../profile/TbTenantProfileService.java | 2 +- .../user/DefaultTbUserSettingsService.java | 202 + .../entitiy/user/DefaultUserService.java | 2 +- .../service/entitiy/user/TbUserService.java | 2 +- .../entitiy/user/TbUserSettingsService.java | 42 + .../bundle/DefaultWidgetsBundleService.java | 2 +- .../bundle/TbWidgetsBundleService.java | 2 +- .../executors/DbCallbackExecutorService.java | 2 +- .../ExternalCallExecutorService.java | 2 +- .../GrpcCallbackExecutorService.java | 2 +- .../NotificationExecutorService.java | 33 + .../SharedEventLoopGroupService.java | 2 +- .../DefaultGatewayNotificationsService.java | 2 +- .../GatewayNotificationsService.java | 2 +- ...stractCassandraDatabaseUpgradeService.java | 2 +- .../AbstractSqlTsDatabaseUpgradeService.java | 2 +- ...assandraAbstractDatabaseSchemaService.java | 2 +- .../install/CassandraKeyspaceService.java | 2 +- .../CassandraTsDatabaseSchemaService.java | 2 +- .../CassandraTsDatabaseUpgradeService.java | 2 +- ...assandraTsLatestDatabaseSchemaService.java | 2 +- .../DatabaseEntitiesUpgradeService.java | 2 +- .../service/install/DatabaseHelper.java | 2 +- .../install/DatabaseSchemaService.java | 2 +- .../install/DatabaseTsUpgradeService.java | 2 +- .../install/DbUpgradeExecutorService.java | 2 +- .../DefaultSystemDataLoaderService.java | 50 +- .../install/EntityDatabaseSchemaService.java | 2 +- .../service/install/InstallScripts.java | 54 +- .../service/install/NoSqlKeyspaceService.java | 2 +- .../SqlAbstractDatabaseSchemaService.java | 2 +- .../install/SqlDatabaseUpgradeService.java | 38 +- .../SqlEntityDatabaseSchemaService.java | 2 +- .../install/SqlTsDatabaseSchemaService.java | 2 +- .../install/SqlTsDatabaseUpgradeService.java | 2 +- .../install/SystemDataLoaderService.java | 5 +- .../TbRuleEngineQueueConfigService.java | 5 +- .../TimescaleTsDatabaseSchemaService.java | 2 +- .../TimescaleTsDatabaseUpgradeService.java | 2 +- .../install/TsDatabaseSchemaService.java | 2 +- .../TsLatestDatabaseSchemaService.java | 2 +- .../install/cql/CQLStatementsParser.java | 2 +- .../install/cql/CassandraDbHelper.java | 2 +- .../CassandraEntitiesToSqlMigrateService.java | 2 +- .../install/migrate/CassandraToSqlColumn.java | 2 +- .../migrate/CassandraToSqlColumnData.java | 2 +- .../migrate/CassandraToSqlColumnType.java | 2 +- .../migrate/CassandraToSqlEventTsColumn.java | 2 +- .../install/migrate/CassandraToSqlTable.java | 2 +- .../CassandraTsLatestToSqlMigrateService.java | 2 +- .../migrate/EntitiesMigrateService.java | 2 +- .../migrate/TsLatestMigrateService.java | 2 +- .../service/install/sql/SqlDbHelper.java | 2 +- .../install/update/CacheCleanupService.java | 2 +- .../install/update/DataUpdateService.java | 2 +- .../update/DefaultCacheCleanupService.java | 2 +- .../update/DefaultDataUpdateService.java | 13 +- .../install/update/PaginatedUpdater.java | 2 +- .../install/update/RateLimitsUpdater.java | 2 +- .../server/service/lwm2m/LwM2MService.java | 2 +- .../service/lwm2m/LwM2MServiceImpl.java | 2 +- .../service/mail/DefaultMailService.java | 46 +- .../service/mail/MailExecutorService.java | 2 +- .../mail/PasswordResetExecutorService.java | 2 +- .../DefaultNotificationCenter.java | 422 + .../DefaultNotificationSchedulerService.java | 175 + .../NotificationProcessingContext.java | 137 + .../NotificationSchedulerService.java | 25 + .../channels/EmailNotificationChannel.java | 65 + .../channels/NotificationChannel.java | 33 + .../channels/SlackNotificationChannel.java | 60 + .../channels/SmsNotificationChannel.java | 61 + .../DefaultNotificationRuleProcessor.java | 195 + .../AlarmAssignmentTriggerProcessor.java | 84 + .../trigger/AlarmCommentTriggerProcessor.java | 92 + .../rule/trigger/AlarmTriggerProcessor.java | 120 + .../ApiUsageLimitTriggerProcessor.java | 59 + .../DeviceActivityTriggerProcessor.java | 82 + .../EntitiesLimitTriggerProcessor.java | 60 + .../trigger/EntityActionTriggerProcessor.java | 93 + .../NewPlatformVersionTriggerProcessor.java | 58 + .../NotificationRuleTriggerProcessor.java | 35 + ...mponentLifecycleEventTriggerProcessor.java | 107 + ...neMsgNotificationRuleTriggerProcessor.java | 27 + .../ota/DefaultOtaPackageStateService.java | 2 +- .../service/ota/OtaPackageStateService.java | 2 +- .../AbstractPartitionBasedService.java | 7 +- .../profile/DefaultTbAssetProfileCache.java | 2 +- .../profile/DefaultTbDeviceProfileCache.java | 2 +- .../service/profile/TbAssetProfileCache.java | 2 +- .../service/profile/TbDeviceProfileCache.java | 2 +- .../query/DefaultEntityQueryService.java | 8 +- .../service/query/EntityQueryService.java | 5 +- .../queue/DefaultQueueRoutingInfoService.java | 2 +- .../queue/DefaultTbClusterService.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 81 +- .../DefaultTbRuleEngineConsumerService.java | 8 +- .../DefaultTenantRoutingInfoService.java | 2 +- .../service/queue/TbCoreConsumerService.java | 2 +- .../service/queue/TbCoreConsumerStats.java | 2 +- .../service/queue/TbMsgPackCallback.java | 2 +- .../queue/TbMsgPackProcessingContext.java | 2 +- .../service/queue/TbMsgProfilerInfo.java | 2 +- .../server/service/queue/TbPackCallback.java | 2 +- .../queue/TbPackProcessingContext.java | 2 +- .../queue/TbRuleEngineConsumerService.java | 2 +- .../queue/TbRuleEngineConsumerStats.java | 2 +- .../service/queue/TbRuleNodeProfilerInfo.java | 2 +- .../queue/TbTenantRuleEngineStats.java | 2 +- .../TbTopicWithConsumerPerPartition.java | 2 +- .../processing/AbstractConsumerService.java | 9 +- .../AbstractTbRuleEngineSubmitStrategy.java | 2 +- .../BatchTbRuleEngineSubmitStrategy.java | 2 +- .../BurstTbRuleEngineSubmitStrategy.java | 2 +- .../service/queue/processing/IdMsgPair.java | 2 +- ...lByEntityIdTbRuleEngineSubmitStrategy.java | 2 +- ...riginatorIdTbRuleEngineSubmitStrategy.java | 2 +- ...lByTenantIdTbRuleEngineSubmitStrategy.java | 2 +- .../SequentialTbRuleEngineSubmitStrategy.java | 2 +- .../TbRuleEngineProcessingDecision.java | 2 +- .../TbRuleEngineProcessingResult.java | 2 +- .../TbRuleEngineProcessingStrategy.java | 2 +- ...TbRuleEngineProcessingStrategyFactory.java | 2 +- .../TbRuleEngineSubmitStrategy.java | 2 +- .../TbRuleEngineSubmitStrategyFactory.java | 2 +- .../resource/DefaultTbResourceService.java | 84 +- .../service/resource/TbResourceService.java | 2 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 2 +- .../rpc/DefaultTbRuleEngineRpcService.java | 2 +- .../rpc/FromDeviceRpcResponseActorMsg.java | 2 +- .../service/rpc/LocalRequestMetaData.java | 2 +- .../server/service/rpc/RemoveRpcActorMsg.java | 2 +- .../service/rpc/TbCoreDeviceRpcService.java | 2 +- .../server/service/rpc/TbRpcService.java | 2 +- .../rpc/TbRuleEngineDeviceRpcService.java | 2 +- .../rpc/ToDeviceRpcRequestActorMsg.java | 2 +- .../rule/DefaultTbRuleChainService.java | 2 +- .../service/rule/TbRuleChainService.java | 2 +- .../script/RuleNodeJsScriptEngine.java | 2 +- .../service/script/RuleNodeScriptEngine.java | 2 +- .../script/RuleNodeTbelScriptEngine.java | 4 +- .../service/security/AccessValidator.java | 4 +- .../service/security/ValidationCallback.java | 10 +- .../service/security/ValidationResult.java | 2 +- .../security/ValidationResultCode.java | 2 +- .../auth/AbstractJwtAuthenticationToken.java | 2 +- .../auth/DefaultTokenOutdatingService.java | 2 +- .../security/auth/JwtAuthenticationToken.java | 2 +- .../security/auth/MfaAuthenticationToken.java | 2 +- .../auth/RefreshAuthenticationToken.java | 2 +- .../security/auth/TokenOutdatingService.java | 2 +- .../auth/jwt/JwtAuthenticationProvider.java | 2 +- ...wtTokenAuthenticationProcessingFilter.java | 2 +- .../RefreshTokenAuthenticationProvider.java | 2 +- .../jwt/RefreshTokenProcessingFilter.java | 2 +- .../auth/jwt/RefreshTokenRequest.java | 2 +- .../auth/jwt/SkipPathRequestMatcher.java | 2 +- .../extractor/JwtHeaderTokenExtractor.java | 2 +- .../jwt/extractor/JwtQueryTokenExtractor.java | 2 +- .../auth/jwt/extractor/TokenExtractor.java | 2 +- .../settings/DefaultJwtSettingsService.java | 2 +- .../settings/DefaultJwtSettingsValidator.java | 6 +- .../settings/InstallJwtSettingsValidator.java | 2 +- .../auth/jwt/settings/JwtSettingsService.java | 2 +- .../jwt/settings/JwtSettingsValidator.java | 2 +- .../auth/mfa/DefaultTwoFactorAuthService.java | 2 +- .../auth/mfa/TwoFactorAuthService.java | 2 +- .../mfa/config/DefaultTwoFaConfigManager.java | 2 +- .../auth/mfa/config/TwoFaConfigManager.java | 2 +- .../auth/mfa/provider/TwoFaProvider.java | 2 +- .../impl/BackupCodeTwoFaProvider.java | 4 +- .../mfa/provider/impl/EmailTwoFaProvider.java | 2 +- .../provider/impl/OtpBasedTwoFaProvider.java | 2 +- .../mfa/provider/impl/SmsTwoFaProvider.java | 2 +- .../mfa/provider/impl/TotpTwoFaProvider.java | 2 +- .../oauth2/AbstractOAuth2ClientMapper.java | 2 +- .../auth/oauth2/AppleOAuth2ClientMapper.java | 2 +- .../auth/oauth2/BasicMapperUtils.java | 2 +- .../auth/oauth2/BasicOAuth2ClientMapper.java | 2 +- .../security/auth/oauth2/CookieUtils.java | 2 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 2 +- .../auth/oauth2/GithubOAuth2ClientMapper.java | 2 +- ...eOAuth2AuthorizationRequestRepository.java | 2 +- .../auth/oauth2/OAuth2ClientMapper.java | 2 +- .../oauth2/OAuth2ClientMapperProvider.java | 2 +- .../Oauth2AuthenticationFailureHandler.java | 2 +- .../Oauth2AuthenticationSuccessHandler.java | 2 +- .../auth/oauth2/TbOAuth2ParameterNames.java | 2 +- .../security/auth/rest/LoginRequest.java | 2 +- .../security/auth/rest/LoginResponse.java | 2 +- .../auth/rest/PublicLoginRequest.java | 2 +- .../auth/rest/RestAuthenticationDetails.java | 2 +- .../rest/RestAuthenticationDetailsSource.java | 2 +- .../auth/rest/RestAuthenticationProvider.java | 2 +- ...RestAwareAuthenticationFailureHandler.java | 2 +- ...RestAwareAuthenticationSuccessHandler.java | 2 +- .../auth/rest/RestLoginProcessingFilter.java | 2 +- .../rest/RestPublicLoginProcessingFilter.java | 2 +- .../device/DefaultDeviceAuthService.java | 2 +- .../AuthMethodNotSupportedException.java | 2 +- .../exception/JwtExpiredTokenException.java | 2 +- .../UserPasswordExpiredException.java | 2 +- .../security/model/ActivateUserRequest.java | 2 +- .../security/model/ChangePasswordRequest.java | 2 +- .../model/ResetPasswordEmailRequest.java | 2 +- .../security/model/ResetPasswordRequest.java | 2 +- .../service/security/model/SecurityUser.java | 2 +- .../service/security/model/UserPrincipal.java | 2 +- .../security/model/token/AccessJwtToken.java | 2 +- .../security/model/token/JwtTokenFactory.java | 2 +- .../model/token/OAuth2AppTokenFactory.java | 2 +- .../model/token/RawAccessJwtToken.java | 2 +- .../permission/AbstractPermissions.java | 2 +- .../permission/AccessControlService.java | 2 +- .../permission/CustomerUserPermissions.java | 11 +- .../DefaultAccessControlService.java | 2 +- .../security/permission/Operation.java | 2 +- .../permission/PermissionChecker.java | 2 +- .../security/permission/Permissions.java | 2 +- .../service/security/permission/Resource.java | 22 +- .../permission/SysAdminPermissions.java | 3 +- .../permission/TenantAdminPermissions.java | 3 +- .../system/DefaultSystemSecurityService.java | 5 +- .../system/SystemSecurityService.java | 2 +- .../DefaultDeviceSessionCacheService.java | 2 +- .../session/DeviceSessionCacheService.java | 2 +- .../service/session/SessionCaffeineCache.java | 2 +- .../service/session/SessionRedisCache.java | 2 +- .../service/slack/DefaultSlackService.java | 164 + .../server/service/sms/AbstractSmsSender.java | 2 +- .../service/sms/DefaultSmsSenderFactory.java | 2 +- .../server/service/sms/DefaultSmsService.java | 2 +- .../service/sms/SmsExecutorService.java | 2 +- .../server/service/sms/aws/AwsSmsSender.java | 2 +- .../service/sms/smpp/SmppSmsSender.java | 2 +- .../service/sms/twilio/TwilioSmsSender.java | 2 +- .../state/DefaultDeviceStateService.java | 14 +- .../server/service/state/DeviceState.java | 2 +- .../server/service/state/DeviceStateData.java | 2 +- .../service/state/DeviceStateService.java | 2 +- .../service/stats/DefaultJsInvokeStats.java | 2 +- .../DefaultRuleEngineStatisticsService.java | 18 +- .../stats/RuleEngineStatisticsService.java | 2 +- .../DefaultSubscriptionManagerService.java | 147 +- ...efaultTbEntityDataSubscriptionService.java | 81 +- .../DefaultTbLocalSubscriptionService.java | 33 +- .../subscription/ReadTsKvQueryInfo.java | 4 +- .../SubscriptionErrorCode.java | 4 +- .../SubscriptionManagerService.java | 14 +- .../SubscriptionServiceStatistics.java | 2 +- .../subscription/TbAbstractDataSubCtx.java | 16 +- .../subscription/TbAbstractSubCtx.java | 21 +- .../subscription/TbAlarmCountSubCtx.java | 67 + .../subscription/TbAlarmDataSubCtx.java | 45 +- .../subscription/TbAlarmsSubscription.java | 11 +- .../subscription/TbAttributeSubscription.java | 8 +- .../TbAttributeSubscriptionScope.java | 2 +- .../subscription/TbEntityCountSubCtx.java | 12 +- .../subscription/TbEntityDataSubCtx.java | 20 +- .../TbEntityDataSubscriptionService.java | 22 +- .../TbLocalSubscriptionService.java | 9 +- .../service/subscription/TbSubscription.java | 4 +- .../subscription/TbSubscriptionType.java | 4 +- .../subscription/TbSubscriptionUtils.java | 104 +- .../TbTimeseriesSubscription.java | 8 +- .../DefaultEntitiesExportImportService.java | 11 +- .../sync/ie/EntitiesExportImportService.java | 2 +- .../DefaultExportableEntitiesService.java | 2 +- .../ie/exporting/EntityExportService.java | 2 +- .../exporting/ExportableEntitiesService.java | 2 +- .../ie/exporting/impl/AssetExportService.java | 2 +- .../impl/AssetProfileExportService.java | 2 +- .../impl/BaseEntityExportService.java | 2 +- .../impl/DashboardExportService.java | 2 +- .../impl/DefaultEntityExportService.java | 2 +- .../exporting/impl/DeviceExportService.java | 2 +- .../impl/DeviceProfileExportService.java | 2 +- .../impl/EntityViewExportService.java | 2 +- .../impl/RuleChainExportService.java | 2 +- .../impl/WidgetsBundleExportService.java | 2 +- .../ie/importing/EntityImportService.java | 2 +- .../csv/AbstractBulkImportService.java | 2 +- .../ie/importing/csv/ImportedEntityInfo.java | 2 +- .../ie/importing/impl/AssetImportService.java | 2 +- .../impl/AssetProfileImportService.java | 2 +- .../impl/BaseEntityImportService.java | 2 +- .../importing/impl/CustomerImportService.java | 2 +- .../impl/DashboardImportService.java | 2 +- .../importing/impl/DeviceImportService.java | 2 +- .../impl/DeviceProfileImportService.java | 2 +- .../impl/EntityViewImportService.java | 2 +- .../impl/ImportServiceException.java | 2 +- .../impl/MissingEntityException.java | 2 +- .../impl/RuleChainImportService.java | 2 +- .../impl/WidgetsBundleImportService.java | 2 +- .../DefaultEntitiesVersionControlService.java | 4 +- .../DefaultGitVersionControlQueueService.java | 7 +- .../vc/EntitiesVersionControlService.java | 2 +- .../vc/GitVersionControlQueueService.java | 2 +- .../service/sync/vc/LoadEntityException.java | 2 +- ...AbstractVersionControlSettingsService.java | 2 +- .../sync/vc/VersionControlTaskCacheEntry.java | 2 +- .../vc/VersionControlTaskCaffeineCache.java | 2 +- .../sync/vc/VersionControlTaskRedisCache.java | 2 +- .../AutoCommitSettingsCaffeineCache.java | 2 +- .../AutoCommitSettingsRedisCache.java | 2 +- .../DefaultTbAutoCommitSettingsService.java | 2 +- .../TbAutoCommitSettingsService.java | 2 +- .../vc/data/ClearRepositoryGitRequest.java | 2 +- .../sync/vc/data/CommitGitRequest.java | 2 +- .../vc/data/ComplexEntitiesExportCtx.java | 2 +- .../sync/vc/data/ContentsDiffGitRequest.java | 2 +- .../vc/data/EntitiesContentGitRequest.java | 2 +- .../sync/vc/data/EntitiesExportCtx.java | 2 +- .../sync/vc/data/EntitiesImportCtx.java | 4 +- .../sync/vc/data/EntityContentGitRequest.java | 2 +- .../sync/vc/data/EntityTypeExportCtx.java | 2 +- .../sync/vc/data/ListBranchesGitRequest.java | 2 +- .../sync/vc/data/ListEntitiesGitRequest.java | 2 +- .../sync/vc/data/ListVersionsGitRequest.java | 2 +- .../sync/vc/data/PendingGitRequest.java | 2 +- .../service/sync/vc/data/ReimportTask.java | 2 +- .../sync/vc/data/SimpleEntitiesExportCtx.java | 2 +- .../sync/vc/data/VersionsDiffGitRequest.java | 2 +- .../service/sync/vc/data/VoidGitRequest.java | 2 +- .../DefaultTbRepositorySettingsService.java | 2 +- .../RepositorySettingsCaffeineCache.java | 2 +- .../RepositorySettingsRedisCache.java | 2 +- .../TbRepositorySettingsService.java | 2 +- .../system/DefaultSystemInfoService.java | 214 + .../service/system/SystemInfoService.java | 25 + .../AbstractSubscriptionService.java | 50 +- .../telemetry/AlarmSubscriptionService.java | 2 +- .../service/telemetry/AttributeData.java | 2 +- .../DefaultAlarmSubscriptionService.java | 211 +- .../DefaultTelemetrySubscriptionService.java | 95 +- .../telemetry/InternalTelemetryService.java | 4 +- .../TelemetrySubscriptionService.java | 2 +- .../server/service/telemetry/TsData.java | 2 +- .../BasicCredentialsValidationResult.java | 2 +- .../DefaultTbCoreToTransportService.java | 2 +- .../transport/DefaultTransportApiService.java | 5 +- .../transport/TbCoreToTransportService.java | 2 +- .../transport/TbCoreTransportApiService.java | 2 +- .../transport/TransportApiService.java | 2 +- .../msg/TransportToDeviceActorMsgWrapper.java | 2 +- .../service/ttl/AbstractCleanUpService.java | 2 +- .../service/ttl/AlarmsCleanUpService.java | 3 +- .../service/ttl/AuditLogsCleanUpService.java | 2 +- .../service/ttl/EdgeEventsCleanUpService.java | 2 +- .../service/ttl/EventsCleanUpService.java | 2 +- .../ttl/NotificationsCleanUpService.java | 71 + .../service/ttl/TimeseriesCleanUpService.java | 2 +- .../service/ttl/rpc/RpcCleanUpService.java | 2 +- .../service/update/DefaultUpdateService.java | 31 +- .../server/service/update/UpdateService.java | 2 +- .../DefaultWebSocketService.java} | 408 +- .../{telemetry => ws}/SessionEvent.java | 4 +- .../WebSocketMsgEndpoint.java} | 13 +- .../WebSocketService.java} | 17 +- .../WebSocketSessionRef.java} | 32 +- .../service/ws/WebSocketSessionType.java | 38 + .../{telemetry => ws}/WsSessionMetaData.java | 12 +- .../DefaultNotificationCommandsHandler.java | 248 + .../NotificationCommandsHandler.java | 37 + .../cmd/MarkAllNotificationsAsReadCmd.java | 27 + .../cmd/MarkNotificationsAsReadCmd.java | 31 + .../cmd/NotificationCmdsWrapper.java | 33 + .../cmd/NotificationsCountSubCmd.java | 27 + .../notification/cmd/NotificationsSubCmd.java | 28 + .../cmd/NotificationsUnsubCmd.java | 28 + .../cmd/UnreadNotificationsCountUpdate.java | 46 + .../cmd/UnreadNotificationsUpdate.java | 55 + .../service/ws/notification/cmd/WsCmd.java | 20 + .../sub/NotificationRequestUpdate.java | 31 + .../notification/sub/NotificationUpdate.java | 44 + .../sub/NotificationsCountSubscription.java | 47 + .../sub/NotificationsSubscription.java | 81 + .../sub/NotificationsSubscriptionUpdate.java | 36 + .../{ => ws}/telemetry/TelemetryFeature.java | 4 +- .../telemetry/TelemetryWebSocketTextMsg.java | 7 +- .../cmd/TelemetryPluginCmdsWrapper.java | 28 +- .../cmd/v1/AttributesSubscriptionCmd.java | 6 +- .../telemetry/cmd/v1/GetHistoryCmd.java | 4 +- .../telemetry/cmd/v1/SubscriptionCmd.java | 6 +- .../telemetry/cmd/v1/TelemetryPluginCmd.java | 4 +- .../cmd/v1/TimeseriesSubscriptionCmd.java | 6 +- .../telemetry/cmd/v2/AggHistoryCmd.java | 4 +- .../{ => ws}/telemetry/cmd/v2/AggKey.java | 4 +- .../telemetry/cmd/v2/AggTimeSeriesCmd.java | 4 +- .../ws/telemetry/cmd/v2/AlarmCountCmd.java | 34 + .../cmd/v2/AlarmCountUnsubscribeCmd.java | 25 + .../ws/telemetry/cmd/v2/AlarmCountUpdate.java | 53 + .../telemetry/cmd/v2/AlarmDataCmd.java | 4 +- .../cmd/v2/AlarmDataUnsubscribeCmd.java | 4 +- .../telemetry/cmd/v2/AlarmDataUpdate.java | 6 +- .../{ => ws}/telemetry/cmd/v2/CmdUpdate.java | 4 +- .../telemetry/cmd/v2/CmdUpdateType.java | 9 +- .../{ => ws}/telemetry/cmd/v2/DataCmd.java | 5 +- .../{ => ws}/telemetry/cmd/v2/DataUpdate.java | 6 +- .../telemetry/cmd/v2/EntityCountCmd.java | 5 +- .../cmd/v2/EntityCountUnsubscribeCmd.java | 4 +- .../telemetry/cmd/v2/EntityCountUpdate.java | 10 +- .../telemetry/cmd/v2/EntityDataCmd.java | 4 +- .../cmd/v2/EntityDataUnsubscribeCmd.java | 4 +- .../telemetry/cmd/v2/EntityDataUpdate.java | 6 +- .../telemetry/cmd/v2/EntityHistoryCmd.java | 4 +- .../{ => ws}/telemetry/cmd/v2/GetTsCmd.java | 4 +- .../telemetry/cmd/v2/LatestValueCmd.java | 4 +- .../telemetry/cmd/v2/TimeSeriesCmd.java | 4 +- .../telemetry/cmd/v2/UnsubscribeCmd.java | 4 +- .../sub/AlarmSubscriptionUpdate.java | 28 +- .../telemetry/sub/SubscriptionState.java | 6 +- .../sub/TelemetrySubscriptionUpdate.java | 5 +- ...ngfoxHandlerProviderBeanPostProcessor.java | 2 +- .../thingsboard/server/utils/CsvUtils.java | 2 +- .../utils/EventDeduplicationExecutor.java | 85 - .../server/utils/LwM2mObjectModelUtils.java | 114 + .../thingsboard/server/utils/MiscUtils.java | 2 +- .../server/utils/TypeCastUtil.java | 2 +- application/src/main/resources/logback.xml | 2 +- .../templates/2fa.verification.code.ftl | 2 +- .../resources/templates/account.activated.ftl | 2 +- .../resources/templates/account.lockout.ftl | 2 +- .../main/resources/templates/activation.ftl | 2 +- .../templates/password.was.reset.ftl | 2 +- .../resources/templates/reset.password.ftl | 2 +- .../resources/templates/state.disabled.ftl | 2 +- .../resources/templates/state.enabled.ftl | 2 +- .../resources/templates/state.warning.ftl | 2 +- .../src/main/resources/templates/test.ftl | 2 +- .../src/main/resources/thingsboard.yml | 34 +- .../DeviceActorMessageProcessorTest.java | 2 +- .../server/actors/stats/StatsActorTest.java | 2 +- .../actors/stats/StatsPersistMsgTest.java | 2 +- ...CaffeineCacheDefaultConfigurationTest.java | 2 +- .../controller/AbstractControllerTest.java | 25 +- .../AbstractInMemoryStorageTest.java | 2 +- .../controller/AbstractNotifyEntityTest.java | 8 +- .../AbstractRuleEngineControllerTest.java | 2 +- .../server/controller/AbstractWebTest.java | 214 +- .../controller/BaseAdminControllerTest.java | 24 +- .../BaseAlarmCommentControllerTest.java | 373 + .../controller/BaseAlarmControllerTest.java | 249 +- .../controller/BaseAssetControllerTest.java | 39 +- .../BaseAssetProfileControllerTest.java | 3 +- .../BaseAuditLogControllerTest.java | 2 +- .../controller/BaseAuthControllerTest.java | 2 +- ...BaseComponentDescriptorControllerTest.java | 2 +- .../BaseCustomerControllerTest.java | 4 +- .../BaseDashboardControllerTest.java | 46 +- .../controller/BaseDeviceControllerTest.java | 45 +- .../BaseDeviceProfileControllerTest.java | 27 +- .../controller/BaseEdgeControllerTest.java | 156 +- .../BaseEdgeEventControllerTest.java | 2 +- .../BaseEntityQueryControllerTest.java | 193 +- .../BaseEntityRelationControllerTest.java | 2 +- .../BaseEntityViewControllerTest.java | 41 +- .../controller/BaseHomePageApiTest.java | 485 + .../BaseOtaPackageControllerTest.java | 2 +- .../controller/BaseRpcControllerTest.java | 2 +- .../BaseRuleChainControllerTest.java | 4 +- .../BaseTbResourceControllerTest.java | 2 +- .../controller/BaseTenantControllerTest.java | 9 +- .../BaseTenantProfileControllerTest.java | 7 +- .../controller/BaseUserControllerTest.java | 735 +- .../controller/BaseWebsocketApiTest.java | 95 +- .../BaseWidgetTypeControllerTest.java | 2 +- .../BaseWidgetsBundleControllerTest.java | 4 +- .../controller/TbTestWebSocketClient.java | 84 +- .../controller/TwoFactorAuthConfigTest.java | 22 +- .../server/controller/TwoFactorAuthTest.java | 2 +- .../sql/AdminControllerSqlTest.java | 2 +- .../sql/AlarmCommentControllerSqlTest.java | 23 + .../sql/AlarmControllerSqlTest.java | 2 +- .../sql/AssetControllerSqlTest.java | 2 +- .../sql/AssetProfileControllerSqlTest.java | 2 +- .../sql/AuditLogControllerSqlTest.java | 2 +- .../controller/sql/AuthControllerSqlTest.java | 2 +- .../ComponentDescriptorControllerSqlTest.java | 2 +- .../sql/CustomerControllerSqlTest.java | 2 +- .../sql/DashboardControllerSqlTest.java | 2 +- .../sql/DeviceControllerSqlTest.java | 2 +- .../sql/DeviceProfileControllerSqlTest.java | 2 +- .../controller/sql/EdgeControllerSqlTest.java | 2 +- .../sql/EdgeEventControllerSqlTest.java | 2 +- .../sql/EntityQueryControllerSqlTest.java | 2 +- .../sql/EntityRelationControllerSqlTest.java | 2 +- .../sql/EntityViewControllerSqlTest.java | 2 +- .../controller/sql/HomePageApiSqlTest.java | 23 + .../sql/OtaPackageControllerSqlTest.java | 2 +- .../controller/sql/RpcControllerTest.java | 2 +- .../sql/RuleChainControllerSqlTest.java | 2 +- .../sql/TbResourceControllerSqlTest.java | 2 +- .../sql/TenantControllerSqlTest.java | 2 +- .../sql/TenantProfileControllerSqlTest.java | 2 +- .../sql/TwoFactorAuthConfigSqlTest.java | 2 +- .../controller/sql/TwoFactorAuthSqlTest.java | 2 +- .../controller/sql/UserControllerSqlTest.java | 2 +- .../controller/sql/WebsocketApiSqlTest.java | 2 +- .../sql/WidgetTypeControllerSqlTest.java | 2 +- .../sql/WidgetsBundleControllerSqlTest.java | 2 +- .../server/edge/AbstractEdgeTest.java | 121 +- .../server/edge/BaseAlarmEdgeTest.java | 26 +- .../server/edge/BaseAssetEdgeTest.java | 2 +- .../server/edge/BaseAssetProfileEdgeTest.java | 10 +- .../server/edge/BaseCustomerEdgeTest.java | 2 +- .../server/edge/BaseDashboardEdgeTest.java | 2 +- .../server/edge/BaseDeviceEdgeTest.java | 125 +- .../edge/BaseDeviceProfileEdgeTest.java | 27 +- .../thingsboard/server/edge/BaseEdgeTest.java | 2 +- .../server/edge/BaseEntityViewEdgeTest.java | 2 +- .../server/edge/BaseOtaPackageEdgeTest.java | 2 +- .../server/edge/BaseQueueEdgeTest.java | 2 +- .../server/edge/BaseRelationEdgeTest.java | 2 +- .../server/edge/BaseRuleChainEdgeTest.java | 2 +- .../server/edge/BaseTelemetryEdgeTest.java | 56 +- .../server/edge/BaseUserEdgeTest.java | 2 +- .../server/edge/BaseWidgetEdgeTest.java | 2 +- .../server/edge/imitator/EdgeImitator.java | 2 +- .../server/edge/sql/AlarmEdgeSqlTest.java | 2 +- .../server/edge/sql/AssetEdgeSqlTest.java | 2 +- .../edge/sql/AssetProfileEdgeSqlTest.java | 2 +- .../server/edge/sql/CustomerEdgeSqlTest.java | 2 +- .../server/edge/sql/DashboardEdgeSqlTest.java | 2 +- .../server/edge/sql/DeviceEdgeSqlTest.java | 2 +- .../edge/sql/DeviceProfileEdgeSqlTest.java | 2 +- .../server/edge/sql/EdgeSqlTest.java | 2 +- .../edge/sql/EntityViewEdgeSqlTest.java | 2 +- .../edge/sql/OtaPackageEdgeSqlTest.java | 2 +- .../server/edge/sql/QueueEdgeSqlTest.java | 2 +- .../server/edge/sql/RelationEdgeSqlTest.java | 2 +- .../server/edge/sql/RuleChainEdgeSqlTest.java | 2 +- .../server/edge/sql/TelemetryEdgeSqlTest.java | 2 +- .../server/edge/sql/UserEdgeSqlTest.java | 2 +- .../server/edge/sql/WidgetEdgeSqlTest.java | 2 +- .../discovery/HashPartitionServiceTest.java | 2 +- ...AbstractRuleEngineFlowIntegrationTest.java | 2 +- .../sql/RuleEngineFlowSqlIntegrationTest.java | 2 +- ...actRuleEngineLifecycleIntegrationTest.java | 2 +- ...RuleEngineLifecycleSqlIntegrationTest.java | 2 +- .../DefaultTbApiUsageStateServiceTest.java | 13 +- .../RuleChainMsgConstructorTest.java | 2 +- .../alarm/DefaultTbAlarmServiceTest.java | 44 +- .../DefaultTbAlarmCommentServiceTest.java | 114 + .../SqlEntityDatabaseSchemaServiceTest.java | 2 +- .../update/DefaultDataUpdateServiceTest.java | 2 +- .../server/service/mail/TestMailService.java | 58 - .../AbstractNotificationApiTest.java | 210 + .../notification/NotificationApiTest.java | 543 + .../notification/NotificationApiWsClient.java | 133 + .../notification/NotificationRuleApiTest.java | 438 + .../NotificationTargetApiTest.java | 164 + .../NotificationTemplateApiTest.java | 127 + .../queue/DefaultTbClusterServiceTest.java | 5 +- .../queue/TbMsgPackProcessingContextTest.java | 5 +- .../sql/BaseTbResourceServiceTest.java | 34 +- .../service/script/MockJsInvokeService.java | 2 +- .../script/NashornJsInvokeServiceTest.java | 3 +- .../script/RemoteJsInvokeServiceTest.java | 2 +- .../service/script/TbelInvokeServiceTest.java | 2 +- .../security/auth/JwtTokenFactoryTest.java | 2 +- .../security/auth/TokenOutdatingTest.java | 2 +- ...auth2AuthenticationSuccessHandlerTest.java | 2 +- .../service/sms/smpp/SmppSmsSenderTest.java | 2 +- .../SequentialTimeseriesPersistenceTest.java | 2 +- .../state/DefaultDeviceStateServiceTest.java | 2 +- .../sync/ie/BaseExportImportServiceTest.java | 4 +- .../sync/ie/ExportImportServiceSqlTest.java | 2 +- .../service/ttl/EventsCleanUpServiceTest.java | 2 +- .../server/system/BaseHttpDeviceApiTest.java | 2 +- .../server/system/BaseRestApiLimitsTest.java | 2 +- .../server/system/sql/DeviceApiSqlTest.java | 2 +- .../system/sql/RestApiLimitsSqlTest.java | 2 +- .../AbstractTransportIntegrationTest.java | 4 +- .../transport/TransportNoSqlTestSuite.java | 22 +- .../coap/AbstractCoapIntegrationTest.java | 4 +- .../transport/coap/CoapTestCallback.java | 2 +- .../server/transport/coap/CoapTestClient.java | 2 +- .../coap/CoapTestConfigProperties.java | 2 +- ...AbstractCoapAttributesIntegrationTest.java | 16 +- .../CoapAttributesRequestIntegrationTest.java | 2 +- ...pAttributesRequestJsonIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 2 +- .../CoapAttributesUpdatesIntegrationTest.java | 2 +- ...pAttributesUpdatesJsonIntegrationTest.java | 2 +- ...AttributesUpdatesProtoIntegrationTest.java | 2 +- .../coap/claim/CoapClaimDeviceTest.java | 2 +- .../coap/claim/CoapClaimJsonDeviceTest.java | 2 +- .../coap/claim/CoapClaimProtoDeviceTest.java | 2 +- .../CoapProvisionJsonDeviceTest.java | 2 +- .../CoapProvisionProtoDeviceTest.java | 2 +- ...tractCoapServerSideRpcIntegrationTest.java | 12 +- ...apServerSideRpcDefaultIntegrationTest.java | 2 +- .../CoapServerSideRpcJsonIntegrationTest.java | 2 +- ...CoapServerSideRpcProtoIntegrationTest.java | 2 +- .../CoapAttributesIntegrationTest.java | 2 +- .../CoapAttributesJsonIntegrationTest.java | 2 +- .../CoapAttributesProtoIntegrationTest.java | 2 +- ...AbstractCoapTimeseriesIntegrationTest.java | 2 +- ...ractCoapTimeseriesJsonIntegrationTest.java | 2 +- ...actCoapTimeseriesProtoIntegrationTest.java | 2 +- .../CoapTimeseriesNoSqlIntegrationTest.java | 2 +- ...oapTimeseriesNoSqlJsonIntegrationTest.java | 2 +- ...apTimeseriesNoSqlProtoIntegrationTest.java | 2 +- .../sql/CoapTimeseriesSqlIntegrationTest.java | 2 +- .../CoapTimeseriesSqlJsonIntegrationTest.java | 2 +- ...CoapTimeseriesSqlProtoIntegrationTest.java | 2 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 22 +- .../transport/lwm2m/Lwm2mTestHelper.java | 2 +- .../transport/lwm2m/client/FwLwM2MDevice.java | 2 +- .../lwm2m/client/LwM2MLocationParams.java | 2 +- .../lwm2m/client/LwM2MTestClient.java | 8 +- .../client/LwM2mBinaryAppDataContainer.java | 2 +- .../transport/lwm2m/client/LwM2mLocation.java | 2 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 2 +- .../transport/lwm2m/client/Lwm2mServer.java | 2 +- .../lwm2m/client/SimpleLwM2MDevice.java | 2 +- .../transport/lwm2m/client/SwLwM2MDevice.java | 2 +- .../ota/AbstractOtaLwM2MIntegrationTest.java | 2 +- .../ota/sql/OtaLwM2MIntegrationTest.java | 2 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 2 +- .../sql/RpcLwm2mIntegrationCreateTest.java | 2 +- .../sql/RpcLwm2mIntegrationDeleteTest.java | 2 +- .../sql/RpcLwm2mIntegrationDiscoverTest.java | 2 +- .../sql/RpcLwm2mIntegrationExecuteTest.java | 2 +- .../sql/RpcLwm2mIntegrationObserveTest.java | 2 +- .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 2 +- ...pcLwm2mIntegrationWriteAttributesTest.java | 2 +- .../rpc/sql/RpcLwm2mIntegrationWriteTest.java | 2 +- .../AbstractSecurityLwM2MIntegrationTest.java | 2 +- .../sql/NoSecLwM2MIntegrationTest.java | 2 +- .../security/sql/PskLwm2mIntegrationTest.java | 2 +- .../security/sql/RpkLwM2MIntegrationTest.java | 2 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 2 +- .../sql/X509_TrustLwM2MIntegrationTest.java | 2 +- .../LwM2mTransportServerHelperTest.java | 2 +- .../mqtt/AbstractMqttIntegrationTest.java | 32 +- .../mqtt/MqttTestConfigProperties.java | 6 +- .../mqtt/{ => mqttv3}/MqttTestCallback.java | 4 +- .../mqtt/{ => mqttv3}/MqttTestClient.java | 18 +- ...AbstractMqttAttributesIntegrationTest.java | 100 +- ...tBackwardCompatibilityIntegrationTest.java | 6 +- .../MqttAttributesRequestIntegrationTest.java | 6 +- ...tAttributesRequestJsonIntegrationTest.java | 6 +- ...AttributesRequestProtoIntegrationTest.java | 6 +- ...sBackwardCompatibilityIntegrationTest.java | 6 +- .../MqttAttributesUpdatesIntegrationTest.java | 27 +- ...tAttributesUpdatesJsonIntegrationTest.java | 6 +- ...AttributesUpdatesProtoIntegrationTest.java | 6 +- ...tClaimBackwardCompatibilityDeviceTest.java | 4 +- .../claim/MqttClaimDeviceTest.java | 6 +- .../claim/MqttClaimJsonDeviceTest.java | 4 +- .../claim/MqttClaimProtoDeviceTest.java | 6 +- .../AbstractMqttClientConnectionTest.java | 50 + .../client/MqttClientConnectionTest.java | 48 + .../credentials/BasicMqttCredentialsTest.java | 23 +- .../MqttProvisionJsonDeviceTest.java | 10 +- .../MqttProvisionProtoDeviceTest.java | 10 +- ...tractMqttServerSideRpcIntegrationTest.java | 38 +- ...cBackwardCompatibilityIntegrationTest.java | 4 +- ...ttServerSideRpcDefaultIntegrationTest.java | 4 +- .../MqttServerSideRpcJsonIntegrationTest.java | 6 +- ...MqttServerSideRpcProtoIntegrationTest.java | 4 +- .../MqttAttributesIntegrationTest.java | 6 +- .../MqttAttributesJsonIntegrationTest.java | 4 +- .../MqttAttributesProtoIntegrationTest.java | 4 +- ...AbstractMqttTimeseriesIntegrationTest.java | 10 +- ...ractMqttTimeseriesJsonIntegrationTest.java | 16 +- ...actMqttTimeseriesProtoIntegrationTest.java | 20 +- .../MqttTimeseriesNoSqlIntegrationTest.java | 6 +- ...qttTimeseriesNoSqlJsonIntegrationTest.java | 6 +- ...ttTimeseriesNoSqlProtoIntegrationTest.java | 6 +- .../sql/MqttTimeseriesSqlIntegrationTest.java | 6 +- .../MqttTimeseriesSqlJsonIntegrationTest.java | 6 +- ...MqttTimeseriesSqlProtoIntegrationTest.java | 6 +- .../mqtt/mqttv5/AbstractMqttV5Test.java | 21 + .../mqtt/mqttv5/MqttV5TestCallback.java | 110 + .../mqtt/mqttv5/MqttV5TestClient.java | 177 + .../AbstractAttributesMqttV5Test.java | 163 + .../updates/AttributesUpdatesTest.java | 43 + .../upload/AttributesPublishTest.java | 38 + .../mqttv5/claim/AbstractMqttV5ClaimTest.java | 101 + .../mqtt/mqttv5/claim/MqttV5ClaimTest.java | 39 + .../AbstractMqttV5ClientConnectionTest.java | 113 + .../MqttV5ClientConnectionTest.java | 60 + .../AbstractMqttV5ClientPublishTest.java | 77 + .../publish/MqttV5ClientPublishTest.java | 49 + .../AbstractMqttV5ClientSubscriptionTest.java | 66 + .../MqttV5ClientSubscriptionTest.java | 44 + .../AbstractMqttV5ClientUnsubscribeTest.java | 62 + .../MqttV5ClientUnsubscribeTest.java | 44 + .../provision/MqttV5ProvisionDeviceTest.java | 94 + .../mqttv5/rpc/AbstractMqttV5RpcTest.java | 101 + .../mqtt/mqttv5/rpc/MqttV5RpcTest.java | 43 + .../AbstractMqttV5TimeseriesTest.java | 132 + .../timeseries/MqttV5TimeseriesTest.java | 39 + .../AbstractMqttV5ClientSparkplugTest.java | 462 + ...ctMqttV5ClientSparkplugAttributesTest.java | 439 + ...ientSparkplugBAttributesInProfileTest.java | 55 + .../MqttV5ClientSparkplugBAttributesTest.java | 81 + ...ctMqttV5ClientSparkplugConnectionTest.java | 185 + .../MqttV5ClientSparkplugBConnectionTest.java | 82 + .../rpc/AbstractMqttV5RpcSparkplugTest.java | 112 + .../sparkplug/rpc/MqttV5RpcSparkplugTest.java | 61 + ...actMqttV5ClientSparkplugTelemetryTest.java | 113 + .../MqttV5ClientSparkplugBTelemetryTest.java | 56 + .../util/EventDeduplicationExecutorTest.java | 173 - .../resources/application-test.properties | 4 + .../src/test/resources/logback-test.xml | 11 +- common/actor/pom.xml | 4 +- .../server/actors/AbstractTbActor.java | 2 +- .../server/actors/DefaultTbActorSystem.java | 4 +- .../thingsboard/server/actors/Dispatcher.java | 2 +- .../server/actors/InitFailureStrategy.java | 2 +- .../server/actors/JsInvokeStats.java | 2 +- .../server/actors/ProcessFailureStrategy.java | 2 +- .../thingsboard/server/actors/TbActor.java | 5 +- .../server/actors/TbActorCreator.java | 2 +- .../thingsboard/server/actors/TbActorCtx.java | 2 +- .../server/actors/TbActorException.java | 2 +- .../thingsboard/server/actors/TbActorId.java | 2 +- .../server/actors/TbActorMailbox.java | 10 +- .../actors/TbActorNotRegisteredException.java | 2 +- .../thingsboard/server/actors/TbActorRef.java | 2 +- .../server/actors/TbActorSystem.java | 2 +- .../server/actors/TbActorSystemSettings.java | 2 +- .../server/actors/TbEntityActorId.java | 2 +- .../actors/TbRuleNodeUpdateException.java | 2 +- .../server/actors/TbStringActorId.java | 2 +- .../server/actors/ActorSystemTest.java | 2 +- .../server/actors/ActorTestCtx.java | 2 +- .../server/actors/FailedToInitActor.java | 2 +- .../server/actors/IntTbActorMsg.java | 2 +- .../server/actors/SlowCreateActor.java | 2 +- .../server/actors/SlowInitActor.java | 2 +- .../server/actors/TestRootActor.java | 5 +- common/cache/pom.xml | 4 +- .../thingsboard/server/cache/CacheSpecs.java | 2 +- .../server/cache/CacheSpecsMap.java | 2 +- .../cache/CaffeineTbCacheTransaction.java | 2 +- .../cache/CaffeineTbTransactionalCache.java | 2 +- .../server/cache/RedisTbCacheTransaction.java | 2 +- .../cache/RedisTbTransactionalCache.java | 6 +- .../cache/SimpleTbCacheValueWrapper.java | 2 +- .../cache/TBRedisCacheConfiguration.java | 2 +- .../cache/TBRedisClusterConfiguration.java | 2 +- .../cache/TBRedisStandaloneConfiguration.java | 2 +- .../server/cache/TbCacheTransaction.java | 2 +- .../server/cache/TbCacheValueWrapper.java | 2 +- .../cache/TbCaffeineCacheConfiguration.java | 2 +- .../server/cache/TbFSTRedisSerializer.java | 2 +- .../server/cache/TbRedisSerializer.java | 2 +- .../server/cache/TbTransactionalCache.java | 2 +- .../cache/device/DeviceCacheEvictEvent.java | 2 +- .../server/cache/device/DeviceCacheKey.java | 2 +- .../cache/device/DeviceCaffeineCache.java | 2 +- .../server/cache/device/DeviceRedisCache.java | 2 +- .../cache/ota/CaffeineOtaPackageCache.java | 2 +- .../server/cache/ota/OtaPackageDataCache.java | 2 +- .../cache/ota/RedisOtaPackageDataCache.java | 2 +- ...UsersSessionInvalidationCaffeineCache.java | 2 +- .../UsersSessionInvalidationRedisCache.java | 2 +- .../server/cache/CacheSpecsMapTest.java | 2 +- common/cluster-api/pom.xml | 4 +- .../server/cluster/TbClusterService.java | 5 +- .../server/queue/TbQueueAdmin.java | 2 +- .../server/queue/TbQueueCallback.java | 2 +- .../server/queue/TbQueueClusterService.java | 2 +- .../server/queue/TbQueueConsumer.java | 2 +- .../server/queue/TbQueueHandler.java | 2 +- .../thingsboard/server/queue/TbQueueMsg.java | 2 +- .../server/queue/TbQueueMsgDecoder.java | 2 +- .../server/queue/TbQueueMsgHeaders.java | 2 +- .../server/queue/TbQueueMsgMetadata.java | 2 +- .../server/queue/TbQueueProducer.java | 2 +- .../server/queue/TbQueueRequestTemplate.java | 2 +- .../server/queue/TbQueueResponseTemplate.java | 2 +- .../cluster-api/src/main/proto/jsinvoke.proto | 2 +- common/cluster-api/src/main/proto/queue.proto | 60 +- common/coap-server/pom.xml | 4 +- .../server/coapserver/CoapServerContext.java | 2 +- .../server/coapserver/CoapServerService.java | 2 +- .../coapserver/DefaultCoapServerService.java | 2 +- .../TbCoapDtlsCertificateVerifier.java | 2 +- .../TbCoapDtlsSessionInMemoryStorage.java | 2 +- .../coapserver/TbCoapDtlsSessionInfo.java | 2 +- .../server/coapserver/TbCoapDtlsSettings.java | 2 +- .../coapserver/TbCoapServerComponent.java | 2 +- .../TbCoapServerMessageDeliverer.java | 2 +- common/dao-api/pom.xml | 4 +- .../server/dao/alarm/AlarmCommentService.java | 39 + .../dao/alarm/AlarmOperationResult.java | 22 +- .../server/dao/alarm/AlarmService.java | 71 +- .../server/dao/asset/AssetProfileService.java | 5 +- .../server/dao/asset/AssetService.java | 5 +- .../dao/attributes/AttributesService.java | 2 +- .../server/dao/audit/AuditLogService.java | 2 +- .../cassandra/AbstractCassandraCluster.java | 2 +- .../dao/cassandra/CassandraCluster.java | 2 +- .../dao/cassandra/CassandraDriverOptions.java | 2 +- .../cassandra/CassandraInstallCluster.java | 2 +- .../cassandra/guava/DefaultGuavaSession.java | 2 +- .../cassandra/guava/GuavaDriverContext.java | 2 +- .../guava/GuavaMultiPageResultSet.java | 2 +- .../guava/GuavaRequestAsyncProcessor.java | 2 +- .../dao/cassandra/guava/GuavaSession.java | 2 +- .../cassandra/guava/GuavaSessionBuilder.java | 2 +- .../cassandra/guava/GuavaSessionUtils.java | 2 +- .../component/ComponentDescriptorService.java | 2 +- .../server/dao/customer/CustomerService.java | 5 +- .../dao/dashboard/DashboardService.java | 7 +- .../dao/device/ClaimDevicesService.java | 2 +- .../dao/device/DeviceCredentialsService.java | 2 +- .../dao/device/DeviceProfileService.java | 6 +- .../dao/device/DeviceProvisionService.java | 2 +- .../server/dao/device/DeviceService.java | 8 +- .../server/dao/device/claim/ClaimData.java | 2 +- .../dao/device/claim/ClaimResponse.java | 2 +- .../server/dao/device/claim/ClaimResult.java | 2 +- .../dao/device/claim/ReclaimResult.java | 2 +- .../provision/ProvisionFailedException.java | 2 +- .../device/provision/ProvisionRequest.java | 2 +- .../device/provision/ProvisionResponse.java | 2 +- .../provision/ProvisionResponseStatus.java | 2 +- .../server/dao/edge/EdgeEventService.java | 2 +- .../server/dao/edge/EdgeService.java | 5 +- .../server/dao/entity/EntityCountService.java | 26 + .../server/dao/entity/EntityDaoService.java | 35 + .../server/dao/entity/EntityService.java | 15 +- .../dao/entity/EntityServiceRegistry.java | 24 + .../dao/entityview/EntityViewService.java | 5 +- .../server/dao/event/EventService.java | 2 +- .../dao/nosql/CassandraStatementTask.java | 2 +- .../server/dao/nosql/TbResultSet.java | 2 +- .../server/dao/nosql/TbResultSetFuture.java | 2 +- .../NotificationRequestService.java | 56 + .../notification/NotificationRuleService.java | 46 + .../dao/notification/NotificationService.java | 45 + .../NotificationSettingsService.java | 29 + .../NotificationTargetService.java | 54 + .../NotificationTemplateService.java | 39 + .../oauth2/OAuth2ConfigTemplateService.java | 2 +- .../server/dao/oauth2/OAuth2Service.java | 2 +- .../server/dao/oauth2/OAuth2User.java | 2 +- .../server/dao/ota/OtaPackageService.java | 5 +- .../server/dao/queue/QueueService.java | 5 +- .../server/dao/relation/RelationService.java | 2 +- .../server/dao/resource/ResourceService.java | 6 +- .../server/dao/rpc/RpcService.java | 6 +- .../server/dao/rule/RuleChainService.java | 5 +- .../server/dao/rule/RuleNodeStateService.java | 2 +- .../dao/settings/AdminSettingsService.java | 2 +- .../dao/tenant/TbTenantProfileCache.java | 2 +- .../dao/tenant/TenantProfileService.java | 10 +- .../server/dao/tenant/TenantService.java | 5 +- .../dao/timeseries/TimeseriesService.java | 2 +- .../server/dao/usage/UsageInfoService.java | 25 + .../dao/usagerecord/ApiLimitService.java | 25 + .../dao/usagerecord/ApiUsageStateService.java | 5 +- .../server/dao/user/UserService.java | 20 +- .../server/dao/user/UserSettingsService.java | 36 + .../server/dao/util/AsyncTask.java | 2 +- .../server/dao/util/DbTypeInfoComponent.java | 2 +- .../dao/util/DefaultDbTypeInfoComponent.java | 2 +- .../server/dao/util/NoSqlAnyDao.java | 2 +- .../server/dao/util/NoSqlAnyDaoNonCloud.java | 2 +- .../server/dao/util/NoSqlTsDao.java | 2 +- .../server/dao/util/NoSqlTsLatestDao.java | 2 +- .../thingsboard/server/dao/util/SqlDao.java | 2 +- .../thingsboard/server/dao/util/SqlTsDao.java | 2 +- .../server/dao/util/SqlTsLatestAnyDao.java | 2 +- .../server/dao/util/SqlTsLatestDao.java | 2 +- .../dao/util/SqlTsOrTsLatestAnyDao.java | 2 +- .../server/dao/util/TbAutoConfiguration.java | 2 +- .../server/dao/util/TimescaleDBTsDao.java | 2 +- .../dao/util/TimescaleDBTsLatestDao.java | 2 +- .../dao/util/TimescaleDBTsOrTsLatestDao.java | 2 +- .../server/dao/widget/WidgetTypeService.java | 5 +- .../dao/widget/WidgetsBundleService.java | 5 +- common/data/pom.xml | 4 +- .../server/common/data/AdminSettings.java | 2 +- .../server/common/data/ApiFeature.java | 4 +- .../server/common/data/ApiUsageRecordKey.java | 23 +- .../common/data/ApiUsageRecordState.java | 48 + .../server/common/data/ApiUsageState.java | 2 +- .../common/data/ApiUsageStateValue.java | 2 +- .../server/common/data/BaseData.java | 2 +- .../server/common/data/CacheConstants.java | 7 +- .../server/common/data/ClaimRequest.java | 2 +- .../server/common/data/CoapDeviceType.java | 2 +- .../server/common/data/ContactBased.java | 4 +- .../server/common/data/Customer.java | 4 +- .../server/common/data/Dashboard.java | 2 +- .../server/common/data/DashboardInfo.java | 4 +- .../server/common/data/DataConstants.java | 14 +- .../server/common/data/Device.java | 4 +- .../server/common/data/DeviceIdInfo.java | 2 +- .../server/common/data/DeviceInfo.java | 2 +- .../server/common/data/DeviceProfile.java | 10 +- .../server/common/data/DeviceProfileInfo.java | 13 +- .../data/DeviceProfileProvisionType.java | 2 +- .../server/common/data/DeviceProfileType.java | 2 +- .../common/data/DeviceTransportType.java | 2 +- .../server/common/data/DynamicProtoUtils.java | 2 +- .../server/common/data/EdgeUtils.java | 2 +- .../server/common/data/EntityFieldsData.java | 2 +- .../server/common/data/EntityInfo.java | 2 +- .../server/common/data/EntitySubtype.java | 2 +- .../server/common/data/EntityType.java | 37 +- .../server/common/data/EntityView.java | 2 +- .../server/common/data/EntityViewInfo.java | 2 +- .../server/common/data/EventInfo.java | 2 +- .../server/common/data/ExportableEntity.java | 2 +- .../server/common/data/FSTUtils.java | 2 +- ...tateMailMessage.java => FeaturesInfo.java} | 12 +- .../server/common/data/HasAdditionalInfo.java | 2 +- .../server/common/data/HasCustomerId.java | 2 +- .../server/common/data/HasEmail.java | 22 + .../server/common/data/HasLabel.java | 22 + .../server/common/data/HasName.java | 2 +- .../server/common/data/HasOtaPackage.java | 2 +- .../common/data/HasRuleEngineProfile.java | 2 +- .../server/common/data/HasTenantId.java | 2 +- .../server/common/data/HasTitle.java | 22 + .../server/common/data/HomeDashboard.java | 2 +- .../server/common/data/HomeDashboardInfo.java | 2 +- .../server/common/data/OtaPackage.java | 2 +- .../server/common/data/OtaPackageInfo.java | 4 +- .../server/common/data/ResourceType.java | 2 +- .../server/common/data/ResourceUtils.java | 2 +- .../SaveDeviceWithCredentialsRequest.java | 2 +- .../data/SaveOtaPackageInfoRequest.java | 2 +- .../server/common/data/SearchTextBased.java | 2 +- .../SearchTextBasedWithAdditionalInfo.java | 2 +- .../server/common/data/ShortCustomerInfo.java | 2 +- .../server/common/data/StringUtils.java | 23 +- .../server/common/data/SystemInfo.java | 29 + .../server/common/data/SystemInfoData.java | 43 + .../server/common/data/TbProperty.java | 2 +- .../server/common/data/TbResource.java | 2 +- .../server/common/data/TbResourceInfo.java | 2 +- .../common/data/TbTransportService.java | 2 +- .../server/common/data/Tenant.java | 4 +- .../server/common/data/TenantInfo.java | 2 +- .../server/common/data/TenantProfile.java | 2 +- .../server/common/data/TenantProfileType.java | 2 +- .../common/data/TransportPayloadType.java | 2 +- .../server/common/data/UUIDConverter.java | 2 +- .../server/common/data/UpdateMessage.java | 21 +- .../server/common/data/UsageInfo.java | 43 + .../thingsboard/server/common/data/User.java | 39 +- .../server/common/data/UserEmailInfo.java | 39 + .../server/common/data/alarm/Alarm.java | 57 +- .../common/data/alarm/AlarmApiCallResult.java | 94 + .../common/data/alarm/AlarmAssignee.java | 37 + .../data/alarm/AlarmAssigneeUpdate.java | 30 + .../common/data/alarm/AlarmComment.java | 87 + .../common/data/alarm/AlarmCommentInfo.java | 52 + .../common/data/alarm/AlarmCommentType.java | 22 + .../AlarmCreateOrUpdateActiveRequest.java | 95 + .../server/common/data/alarm/AlarmInfo.java | 58 +- .../data/alarm/AlarmModificationRequest.java | 34 + .../data/alarm/AlarmPropagationInfo.java | 46 + .../server/common/data/alarm/AlarmQuery.java | 5 +- .../common/data/alarm/AlarmSearchStatus.java | 26 +- .../common/data/alarm/AlarmSeverity.java | 2 +- .../server/common/data/alarm/AlarmStatus.java | 2 +- .../common/data/alarm/AlarmStatusFilter.java | 122 + .../common/data/alarm/AlarmUpdateRequest.java | 79 + .../server/common/data/alarm/EntityAlarm.java | 4 +- .../server/common/data/asset/Asset.java | 5 +- .../server/common/data/asset/AssetInfo.java | 2 +- .../common/data/asset/AssetProfile.java | 8 +- .../common/data/asset/AssetProfileInfo.java | 19 +- .../common/data/asset/AssetSearchQuery.java | 2 +- .../common/data/audit/ActionStatus.java | 2 +- .../server/common/data/audit/ActionType.java | 9 +- .../server/common/data/audit/AuditLog.java | 2 +- .../common/data/device/DeviceSearchQuery.java | 2 +- .../credentials/BasicMqttCredentials.java | 2 +- .../ProvisionDeviceCredentialsData.java | 2 +- ...wM2MBootstrapClientCredentialWithKeys.java | 2 +- .../lwm2m/AbstractLwM2MClientCredential.java | 2 +- ...AbstractLwM2MClientSecurityCredential.java | 2 +- .../lwm2m/LwM2MBootstrapClientCredential.java | 2 +- .../LwM2MBootstrapClientCredentials.java | 2 +- .../lwm2m/LwM2MClientCredential.java | 2 +- .../lwm2m/LwM2MDeviceCredentials.java | 2 +- .../credentials/lwm2m/LwM2MSecurityMode.java | 2 +- .../lwm2m/NoSecBootstrapClientCredential.java | 2 +- .../lwm2m/NoSecClientCredential.java | 2 +- .../lwm2m/PSKBootstrapClientCredential.java | 2 +- .../lwm2m/PSKClientCredential.java | 2 +- .../lwm2m/RPKBootstrapClientCredential.java | 2 +- .../lwm2m/RPKClientCredential.java | 2 +- .../lwm2m/X509BootstrapClientCredential.java | 2 +- .../lwm2m/X509ClientCredential.java | 2 +- .../CoapDeviceTransportConfiguration.java | 2 +- .../data/DefaultDeviceConfiguration.java | 2 +- .../DefaultDeviceTransportConfiguration.java | 2 +- .../data/device/data/DeviceConfiguration.java | 2 +- .../common/data/device/data/DeviceData.java | 2 +- .../data/DeviceTransportConfiguration.java | 2 +- .../Lwm2mDeviceTransportConfiguration.java | 2 +- .../MqttDeviceTransportConfiguration.java | 2 +- .../common/data/device/data/PowerMode.java | 2 +- .../device/data/PowerSavingConfiguration.java | 2 +- .../SnmpDeviceTransportConfiguration.java | 2 +- .../data/device/profile/AlarmCondition.java | 2 +- .../device/profile/AlarmConditionFilter.java | 2 +- .../profile/AlarmConditionFilterKey.java | 2 +- .../device/profile/AlarmConditionKeyType.java | 2 +- .../device/profile/AlarmConditionSpec.java | 2 +- .../profile/AlarmConditionSpecType.java | 2 +- .../common/data/device/profile/AlarmRule.java | 2 +- .../data/device/profile/AlarmSchedule.java | 2 +- .../device/profile/AlarmScheduleType.java | 2 +- ...esDeviceProfileProvisionConfiguration.java | 2 +- .../data/device/profile/AnyTimeSchedule.java | 2 +- ...esDeviceProfileProvisionConfiguration.java | 2 +- ...apDeviceProfileTransportConfiguration.java | 2 +- .../profile/CoapDeviceTypeConfiguration.java | 2 +- .../device/profile/CustomTimeSchedule.java | 2 +- .../profile/CustomTimeScheduleItem.java | 2 +- .../DefaultCoapDeviceTypeConfiguration.java | 2 +- .../DefaultDeviceProfileConfiguration.java | 2 +- ...ltDeviceProfileTransportConfiguration.java | 3 +- .../device/profile/DeviceProfileAlarm.java | 2 +- .../profile/DeviceProfileConfiguration.java | 2 +- .../device/profile/DeviceProfileData.java | 2 +- .../DeviceProfileProvisionConfiguration.java | 2 +- .../DeviceProfileTransportConfiguration.java | 2 +- ...edDeviceProfileProvisionConfiguration.java | 2 +- .../profile/DurationAlarmConditionSpec.java | 2 +- .../EfentoCoapDeviceTypeConfiguration.java | 2 +- .../JsonTransportPayloadConfiguration.java | 2 +- ...2mDeviceProfileTransportConfiguration.java | 2 +- ...ttDeviceProfileTransportConfiguration.java | 9 +- .../data/device/profile/MqttTopics.java | 2 +- .../ProtoTransportPayloadConfiguration.java | 2 +- .../ProvisionDeviceProfileCredentials.java | 2 +- .../profile/RepeatingAlarmConditionSpec.java | 2 +- .../profile/SimpleAlarmConditionSpec.java | 2 +- ...mpDeviceProfileTransportConfiguration.java | 2 +- .../device/profile/SpecificTimeSchedule.java | 2 +- .../TransportPayloadTypeConfiguration.java | 2 +- .../profile/lwm2m/ObjectAttributes.java | 2 +- .../profile/lwm2m/OtherConfiguration.java | 2 +- .../lwm2m/TelemetryMappingConfiguration.java | 2 +- ...bstractLwM2MBootstrapServerCredential.java | 2 +- .../LwM2MBootstrapServerCredential.java | 2 +- .../bootstrap/LwM2MServerSecurityConfig.java | 2 +- .../LwM2MServerSecurityConfigDefault.java | 2 +- .../NoSecLwM2MBootstrapServerCredential.java | 2 +- .../PSKLwM2MBootstrapServerCredential.java | 2 +- .../RPKLwM2MBootstrapServerCredential.java | 2 +- .../X509LwM2MBootstrapServerCredential.java | 2 +- .../server/common/data/edge/Edge.java | 5 +- .../server/common/data/edge/EdgeEvent.java | 2 +- .../common/data/edge/EdgeEventActionType.java | 6 +- .../common/data/edge/EdgeEventType.java | 2 +- .../server/common/data/edge/EdgeInfo.java | 2 +- .../data/edge/EdgeInstallInstructions.java | 32 + .../common/data/edge/EdgeSearchQuery.java | 2 +- .../entityview/EntityViewSearchQuery.java | 2 +- .../common/data/event/DebugEventFilter.java | 2 +- .../server/common/data/event/ErrorEvent.java | 2 +- .../common/data/event/ErrorEventFilter.java | 2 +- .../server/common/data/event/Event.java | 2 +- .../server/common/data/event/EventFilter.java | 2 +- .../server/common/data/event/EventType.java | 2 +- .../data/event/LifeCycleEventFilter.java | 2 +- .../common/data/event/LifecycleEvent.java | 2 +- .../data/event/RuleChainDebugEvent.java | 2 +- .../data/event/RuleChainDebugEventFilter.java | 2 +- .../common/data/event/RuleNodeDebugEvent.java | 2 +- .../data/event/RuleNodeDebugEventFilter.java | 2 +- .../common/data/event/StatisticsEvent.java | 2 +- .../data/event/StatisticsEventFilter.java | 2 +- .../ApiUsageLimitsExceededException.java | 2 +- .../data/exception/ThingsboardErrorCode.java | 2 +- .../data/exception/ThingsboardException.java | 2 +- .../ThingsboardKafkaClientError.java | 2 +- .../common/data/id/AdminSettingsId.java | 2 +- .../server/common/data/id/AlarmCommentId.java | 39 + .../server/common/data/id/AlarmId.java | 2 +- .../common/data/id/ApiUsageStateId.java | 2 +- .../server/common/data/id/AssetId.java | 2 +- .../server/common/data/id/AssetProfileId.java | 2 +- .../server/common/data/id/AuditLogId.java | 2 +- .../common/data/id/ComponentDescriptorId.java | 2 +- .../server/common/data/id/CustomerId.java | 2 +- .../server/common/data/id/DashboardId.java | 2 +- .../common/data/id/DeviceCredentialsId.java | 2 +- .../server/common/data/id/DeviceId.java | 2 +- .../common/data/id/DeviceProfileId.java | 2 +- .../server/common/data/id/EdgeEventId.java | 2 +- .../server/common/data/id/EdgeId.java | 2 +- .../server/common/data/id/EntityId.java | 2 +- .../common/data/id/EntityIdDeserializer.java | 2 +- .../common/data/id/EntityIdFactory.java | 20 +- .../common/data/id/EntityIdSerializer.java | 2 +- .../server/common/data/id/EntityViewId.java | 2 +- .../server/common/data/id/EventId.java | 2 +- .../server/common/data/id/HasId.java | 2 +- .../server/common/data/id/HasUUID.java | 2 +- .../server/common/data/id/IdBased.java | 2 +- .../data/id/NameLabelAndCustomerDetails.java | 27 + .../server/common/data/id/NodeId.java | 2 +- .../server/common/data/id/NotificationId.java | 36 + .../common/data/id/NotificationRequestId.java | 36 + .../common/data/id/NotificationRuleId.java | 36 + .../common/data/id/NotificationTargetId.java | 36 + .../data/id/NotificationTemplateId.java | 36 + .../OAuth2ClientRegistrationTemplateId.java | 2 +- .../server/common/data/id/OAuth2DomainId.java | 2 +- .../server/common/data/id/OAuth2MobileId.java | 2 +- .../server/common/data/id/OAuth2ParamsId.java | 2 +- .../common/data/id/OAuth2RegistrationId.java | 2 +- .../server/common/data/id/OtaPackageId.java | 2 +- .../server/common/data/id/QueueId.java | 2 +- .../server/common/data/id/RpcId.java | 2 +- .../server/common/data/id/RuleChainId.java | 2 +- .../server/common/data/id/RuleNodeId.java | 2 +- .../common/data/id/RuleNodeStateId.java | 2 +- .../server/common/data/id/TbResourceId.java | 2 +- .../server/common/data/id/TenantId.java | 8 +- .../common/data/id/TenantProfileId.java | 2 +- .../server/common/data/id/UUIDBased.java | 2 +- .../common/data/id/UserAuthSettingsId.java | 2 +- .../common/data/id/UserCredentialsId.java | 2 +- .../server/common/data/id/UserId.java | 2 +- .../server/common/data/id/WidgetTypeId.java | 2 +- .../common/data/id/WidgetsBundleId.java | 2 +- .../server/common/data/kv/AggTsKvEntry.java | 2 +- .../server/common/data/kv/Aggregation.java | 2 +- .../server/common/data/kv/AttributeKey.java | 2 +- .../common/data/kv/AttributeKvEntry.java | 2 +- .../common/data/kv/BaseAttributeKvEntry.java | 2 +- .../common/data/kv/BaseDeleteTsKvQuery.java | 2 +- .../common/data/kv/BaseReadTsKvQuery.java | 2 +- .../server/common/data/kv/BaseTsKvQuery.java | 2 +- .../server/common/data/kv/BasicKvEntry.java | 2 +- .../server/common/data/kv/BasicTsKvEntry.java | 2 +- .../common/data/kv/BooleanDataEntry.java | 2 +- .../server/common/data/kv/DataType.java | 2 +- .../common/data/kv/DeleteTsKvQuery.java | 2 +- .../common/data/kv/DoubleDataEntry.java | 2 +- .../server/common/data/kv/JsonDataEntry.java | 2 +- .../server/common/data/kv/KvEntry.java | 2 +- .../server/common/data/kv/LongDataEntry.java | 2 +- .../server/common/data/kv/ReadTsKvQuery.java | 2 +- .../common/data/kv/ReadTsKvQueryResult.java | 2 +- .../common/data/kv/StringDataEntry.java | 2 +- .../server/common/data/kv/TsKvEntry.java | 2 +- .../common/data/kv/TsKvEntryAggWrapper.java | 2 +- .../data/kv/TsKvLatestRemovingResult.java | 2 +- .../server/common/data/kv/TsKvQuery.java | 2 +- .../common/data/lwm2m/LwM2mConstants.java | 2 +- .../common/data/lwm2m/LwM2mInstance.java | 2 +- .../server/common/data/lwm2m/LwM2mObject.java | 2 +- .../data/lwm2m/LwM2mResourceObserve.java | 2 +- .../notification/AlreadySentException.java | 19 + .../data/notification/Notification.java | 48 + .../NotificationDeliveryMethod.java | 32 + .../notification/NotificationRequest.java | 104 + .../NotificationRequestConfig.java | 30 + .../notification/NotificationRequestInfo.java | 38 + .../NotificationRequestPreview.java | 32 + .../NotificationRequestStats.java | 73 + .../NotificationRequestStatus.java | 17 +- .../data/notification/NotificationStatus.java | 21 + .../data/notification/NotificationType.java | 32 + .../info/AlarmAssignmentNotificationInfo.java | 94 + .../info/AlarmCommentNotificationInfo.java | 81 + .../info/AlarmNotificationInfo.java | 71 + .../info/ApiUsageLimitNotificationInfo.java | 63 + .../info/DeviceActivityNotificationInfo.java | 65 + .../info/EntitiesLimitNotificationInfo.java | 59 + .../info/EntityActionNotificationInfo.java | 71 + .../NewPlatformVersionNotificationInfo.java | 41 + .../notification/info/NotificationInfo.java | 36 + ...mponentLifecycleEventNotificationInfo.java | 63 + .../RuleEngineOriginatedNotificationInfo.java | 54 + .../info/RuleOriginatedNotificationInfo.java | 36 + ...faultNotificationRuleRecipientsConfig.java | 38 + ...latedNotificationRuleRecipientsConfig.java | 38 + .../notification/rule/NotificationRule.java | 79 + .../rule/NotificationRuleConfig.java | 27 + .../rule/NotificationRuleInfo.java | 39 + .../NotificationRuleRecipientsConfig.java | 46 + ...signmentNotificationRuleTriggerConfig.java | 43 + ...mCommentNotificationRuleTriggerConfig.java | 38 + .../AlarmNotificationRuleTriggerConfig.java | 50 + ...ageLimitNotificationRuleTriggerConfig.java | 35 + ...ActivityNotificationRuleTriggerConfig.java | 41 + ...iesLimitNotificationRuleTriggerConfig.java | 36 + ...tyActionNotificationRuleTriggerConfig.java | 36 + ...mVersionNotificationRuleTriggerConfig.java | 30 + .../NotificationRuleTriggerConfig.java | 42 + .../trigger/NotificationRuleTriggerType.java | 43 + ...cleEventNotificationRuleTriggerConfig.java | 41 + .../settings/AccountNotificationSettings.java | 30 + .../NotificationDeliveryMethodConfig.java | 37 + .../settings/NotificationSettings.java | 34 + ...SlackNotificationDeliveryMethodConfig.java | 34 + .../targets/NotificationRecipient.java | 30 + .../targets/NotificationTarget.java | 43 + .../targets/NotificationTargetConfig.java | 43 + .../targets/NotificationTargetType.java | 33 + .../AffectedTenantAdministratorsFilter.java | 28 + .../targets/platform/AffectedUserFilter.java | 28 + .../targets/platform/AllUsersFilter.java | 28 + .../targets/platform/CustomerUsersFilter.java | 34 + .../OriginatorEntityOwnerUsersFilter.java | 28 + ...PlatformUsersNotificationTargetConfig.java | 39 + .../platform/SystemAdministratorsFilter.java | 28 + .../platform/TenantAdministratorsFilter.java | 34 + .../targets/platform/UserListFilter.java | 35 + .../targets/platform/UsersFilter.java | 41 + .../targets/platform/UsersFilterType.java | 38 + .../targets/slack/SlackConversation.java | 61 + .../targets/slack/SlackConversationType.java | 22 + .../slack/SlackNotificationTargetConfig.java | 40 + .../DeliveryMethodNotificationTemplate.java | 61 + ...ailDeliveryMethodNotificationTemplate.java | 56 + .../notification/template/HasSubject.java | 24 + .../template/NotificationTemplate.java | 47 + .../template/NotificationTemplateConfig.java | 32 + .../template/NotificationText.java | 30 + ...ackDeliveryMethodNotificationTemplate.java | 44 + ...SmsDeliveryMethodNotificationTemplate.java | 44 + ...WebDeliveryMethodNotificationTemplate.java | 101 + .../server/common/data/oauth2/MapperType.java | 2 +- .../data/oauth2/OAuth2BasicMapperConfig.java | 2 +- .../common/data/oauth2/OAuth2ClientInfo.java | 2 +- .../OAuth2ClientRegistrationTemplate.java | 2 +- .../data/oauth2/OAuth2CustomMapperConfig.java | 2 +- .../common/data/oauth2/OAuth2Domain.java | 2 +- .../common/data/oauth2/OAuth2DomainInfo.java | 2 +- .../server/common/data/oauth2/OAuth2Info.java | 2 +- .../data/oauth2/OAuth2MapperConfig.java | 2 +- .../common/data/oauth2/OAuth2Mobile.java | 2 +- .../common/data/oauth2/OAuth2MobileInfo.java | 2 +- .../common/data/oauth2/OAuth2Params.java | 2 +- .../common/data/oauth2/OAuth2ParamsInfo.java | 2 +- .../data/oauth2/OAuth2Registration.java | 2 +- .../data/oauth2/OAuth2RegistrationInfo.java | 2 +- .../common/data/oauth2/PlatformType.java | 2 +- .../server/common/data/oauth2/SchemeType.java | 2 +- .../data/oauth2/TenantNameStrategyType.java | 2 +- .../data/objects/AttributesEntityView.java | 2 +- .../data/objects/TelemetryEntityView.java | 2 +- .../common/data/ota/ChecksumAlgorithm.java | 2 +- .../server/common/data/ota/OtaPackageKey.java | 2 +- .../common/data/ota/OtaPackageType.java | 2 +- .../data/ota/OtaPackageUpdateStatus.java | 2 +- .../common/data/ota/OtaPackageUtil.java | 2 +- .../data/page/BasePageDataIterable.java | 2 +- .../server/common/data/page/PageData.java | 2 +- .../common/data/page/PageDataIterable.java | 2 +- .../data/page/PageDataIterableByTenant.java | 2 +- .../PageDataIterableByTenantIdEntityId.java | 2 +- .../server/common/data/page/PageLink.java | 2 +- .../server/common/data/page/SortOrder.java | 2 +- .../server/common/data/page/TimePageLink.java | 2 +- .../data/plugin/ComponentDescriptor.java | 2 +- .../data/plugin/ComponentLifecycleEvent.java | 2 +- .../data/plugin/ComponentLifecycleState.java | 2 +- .../common/data/plugin/ComponentScope.java | 2 +- .../common/data/plugin/ComponentType.java | 2 +- .../common/data/query/AbstractDataQuery.java | 2 +- .../common/data/query/AlarmCountQuery.java | 43 + .../server/common/data/query/AlarmData.java | 20 +- .../common/data/query/AlarmDataPageLink.java | 13 +- .../common/data/query/AlarmDataQuery.java | 2 +- .../data/query/ApiUsageStateFilter.java | 2 +- .../data/query/AssetSearchQueryFilter.java | 2 +- .../common/data/query/AssetTypeFilter.java | 2 +- .../data/query/BooleanFilterPredicate.java | 2 +- .../common/data/query/ComparisonTsValue.java | 2 +- .../data/query/ComplexFilterPredicate.java | 2 +- .../data/query/DeviceSearchQueryFilter.java | 2 +- .../common/data/query/DeviceTypeFilter.java | 2 +- .../common/data/query/DynamicValue.java | 2 +- .../data/query/DynamicValueSourceType.java | 2 +- .../data/query/EdgeSearchQueryFilter.java | 2 +- .../common/data/query/EdgeTypeFilter.java | 2 +- .../common/data/query/EntityCountQuery.java | 2 +- .../server/common/data/query/EntityData.java | 2 +- .../common/data/query/EntityDataPageLink.java | 2 +- .../common/data/query/EntityDataQuery.java | 2 +- .../data/query/EntityDataSortOrder.java | 2 +- .../common/data/query/EntityFilter.java | 2 +- .../common/data/query/EntityFilterType.java | 2 +- .../server/common/data/query/EntityKey.java | 2 +- .../common/data/query/EntityKeyType.java | 2 +- .../common/data/query/EntityKeyValueType.java | 2 +- .../common/data/query/EntityListFilter.java | 2 +- .../common/data/query/EntityNameFilter.java | 2 +- .../data/query/EntitySearchQueryFilter.java | 2 +- .../common/data/query/EntityTypeFilter.java | 2 +- .../query/EntityViewSearchQueryFilter.java | 2 +- .../data/query/EntityViewTypeFilter.java | 2 +- .../data/query/FilterPredicateType.java | 2 +- .../data/query/FilterPredicateValue.java | 2 +- .../server/common/data/query/KeyFilter.java | 2 +- .../common/data/query/KeyFilterPredicate.java | 2 +- .../data/query/NumericFilterPredicate.java | 2 +- .../data/query/RelationsQueryFilter.java | 2 +- .../data/query/SimpleKeyFilterPredicate.java | 2 +- .../common/data/query/SingleEntityFilter.java | 2 +- .../data/query/StringFilterPredicate.java | 2 +- .../server/common/data/query/TsValue.java | 2 +- .../common/data/queue/ProcessingStrategy.java | 2 +- .../data/queue/ProcessingStrategyType.java | 3 +- .../server/common/data/queue/Queue.java | 2 +- .../common/data/queue/SubmitStrategy.java | 2 +- .../common/data/queue/SubmitStrategyType.java | 2 +- .../common/data/relation/EntityRelation.java | 2 +- .../data/relation/EntityRelationInfo.java | 2 +- .../data/relation/EntityRelationsQuery.java | 2 +- .../data/relation/EntitySearchDirection.java | 2 +- .../relation/RelationEntityTypeFilter.java | 2 +- .../data/relation/RelationTypeGroup.java | 2 +- .../relation/RelationsSearchParameters.java | 2 +- .../server/common/data/rpc/Rpc.java | 2 +- .../server/common/data/rpc/RpcError.java | 2 +- .../server/common/data/rpc/RpcStatus.java | 2 +- .../data/rpc/ToDeviceRpcRequestBody.java | 2 +- .../rule/DefaultRuleChainCreateRequest.java | 2 +- .../common/data/rule/NodeConnectionInfo.java | 2 +- .../server/common/data/rule/RuleChain.java | 2 +- .../data/rule/RuleChainConnectionInfo.java | 2 +- .../common/data/rule/RuleChainData.java | 2 +- .../data/rule/RuleChainImportResult.java | 2 +- .../common/data/rule/RuleChainMetaData.java | 2 +- .../data/rule/RuleChainOutputLabelsUsage.java | 2 +- .../common/data/rule/RuleChainType.java | 2 +- .../data/rule/RuleChainUpdateResult.java | 2 +- .../server/common/data/rule/RuleNode.java | 2 +- .../common/data/rule/RuleNodeState.java | 2 +- .../data/rule/RuleNodeUpdateResult.java | 2 +- .../server/common/data/rule/RuleType.java | 2 +- .../server/common/data/rule/Scope.java | 2 +- .../common/data/script/ScriptLanguage.java | 2 +- .../common/data/security/Authority.java | 2 +- .../data/security/DeviceCredentials.java | 2 +- .../security/DeviceCredentialsFilter.java | 2 +- .../data/security/DeviceCredentialsType.java | 2 +- .../data/security/DeviceTokenCredentials.java | 2 +- .../data/security/DeviceX509Credentials.java | 2 +- .../data/security/UserAuthSettings.java | 2 +- .../common/data/security/UserCredentials.java | 27 +- .../event/UserAuthDataChangedEvent.java | 2 +- .../UserCredentialsInvalidationEvent.java | 2 +- .../event/UserSessionInvalidationEvent.java | 2 +- .../common/data/security/model/JwtPair.java | 2 +- .../data/security/model/JwtSettings.java | 2 +- .../common/data/security/model/JwtToken.java | 2 +- .../data/security/model/SecuritySettings.java | 2 +- .../security/model/UserPasswordPolicy.java | 2 +- .../model/mfa/PlatformTwoFaSettings.java | 10 +- .../mfa/account/AccountTwoFaSettings.java | 2 +- .../account/BackupCodeTwoFaAccountConfig.java | 2 +- .../mfa/account/EmailTwoFaAccountConfig.java | 2 +- .../account/OtpBasedTwoFaAccountConfig.java | 2 +- .../mfa/account/SmsTwoFaAccountConfig.java | 6 +- .../mfa/account/TotpTwoFaAccountConfig.java | 6 +- .../model/mfa/account/TwoFaAccountConfig.java | 2 +- .../BackupCodeTwoFaProviderConfig.java | 4 +- .../provider/EmailTwoFaProviderConfig.java | 2 +- .../provider/OtpBasedTwoFaProviderConfig.java | 4 +- .../mfa/provider/SmsTwoFaProviderConfig.java | 6 +- .../mfa/provider/TotpTwoFaProviderConfig.java | 4 +- .../mfa/provider/TwoFaProviderConfig.java | 2 +- .../model/mfa/provider/TwoFaProviderType.java | 2 +- .../settings/AbstractUserDashboardInfo.java | 37 + .../settings/LastVisitedDashboardInfo.java | 37 + .../data/settings/StarredDashboardInfo.java | 36 + .../data/settings/UserDashboardAction.java | 22 + .../data/settings/UserDashboardsInfo.java | 46 + .../common/data/settings/UserSettings.java | 66 + .../settings/UserSettingsCompositeKey.java | 44 + .../data/settings/UserSettingsType.java | 34 + .../AwsSnsSmsProviderConfiguration.java | 2 +- .../config/SmppSmsProviderConfiguration.java | 2 +- .../sms/config/SmsProviderConfiguration.java | 2 +- .../data/sms/config/SmsProviderType.java | 2 +- .../data/sms/config/TestSmsRequest.java | 2 +- .../TwilioSmsProviderConfiguration.java | 2 +- .../server/common/data/sync/JsonTbEntity.java | 2 +- .../data/sync/ie/AttributeExportData.java | 2 +- .../common/data/sync/ie/DeviceExportData.java | 2 +- .../common/data/sync/ie/EntityExportData.java | 2 +- .../data/sync/ie/EntityExportSettings.java | 2 +- .../data/sync/ie/EntityImportResult.java | 4 +- .../data/sync/ie/EntityImportSettings.java | 2 +- .../data/sync/ie/RuleChainExportData.java | 2 +- .../data/sync/ie/WidgetsBundleExportData.java | 2 +- .../importing/csv/BulkImportColumnType.java | 2 +- .../ie/importing/csv/BulkImportRequest.java | 2 +- .../ie/importing/csv/BulkImportResult.java | 2 +- .../data/sync/vc/AutoCommitSettings.java | 2 +- .../common/data/sync/vc/BranchInfo.java | 2 +- .../common/data/sync/vc/EntityDataDiff.java | 2 +- .../common/data/sync/vc/EntityDataInfo.java | 2 +- .../common/data/sync/vc/EntityLoadError.java | 2 +- .../data/sync/vc/EntityTypeLoadResult.java | 2 +- .../common/data/sync/vc/EntityVersion.java | 2 +- .../data/sync/vc/EntityVersionsDiff.java | 2 +- .../data/sync/vc/RepositoryAuthMethod.java | 2 +- .../data/sync/vc/RepositorySettings.java | 2 +- .../data/sync/vc/RepositorySettingsInfo.java | 2 +- .../data/sync/vc/VersionCreationResult.java | 2 +- .../data/sync/vc/VersionLoadResult.java | 2 +- .../data/sync/vc/VersionedEntityInfo.java | 2 +- .../create/AutoVersionCreateConfig.java | 2 +- .../create/ComplexVersionCreateRequest.java | 2 +- .../create/EntityTypeVersionCreateConfig.java | 2 +- .../SingleEntityVersionCreateRequest.java | 2 +- .../sync/vc/request/create/SyncStrategy.java | 2 +- .../request/create/VersionCreateConfig.java | 2 +- .../request/create/VersionCreateRequest.java | 2 +- .../create/VersionCreateRequestType.java | 2 +- .../load/EntityTypeVersionLoadConfig.java | 2 +- .../load/EntityTypeVersionLoadRequest.java | 2 +- .../load/SingleEntityVersionLoadRequest.java | 2 +- .../vc/request/load/VersionLoadConfig.java | 2 +- .../vc/request/load/VersionLoadRequest.java | 2 +- .../request/load/VersionLoadRequestType.java | 2 +- .../DefaultTenantProfileConfiguration.java | 23 +- .../profile/TenantProfileConfiguration.java | 2 +- .../tenant/profile/TenantProfileData.java | 2 +- .../TenantProfileQueueConfiguration.java | 2 +- .../data/transport/resource/ResourceType.java | 2 +- .../snmp/AuthenticationProtocol.java | 2 +- .../data/transport/snmp/PrivacyProtocol.java | 2 +- .../transport/snmp/SnmpCommunicationSpec.java | 2 +- .../data/transport/snmp/SnmpMapping.java | 2 +- .../data/transport/snmp/SnmpMethod.java | 2 +- .../transport/snmp/SnmpProtocolVersion.java | 2 +- ...ltipleMappingsSnmpCommunicationConfig.java | 2 +- ...eatingQueryingSnmpCommunicationConfig.java | 2 +- .../snmp/config/SnmpCommunicationConfig.java | 2 +- ...ibutesQueryingSnmpCommunicationConfig.java | 2 +- ...ributesSettingSnmpCommunicationConfig.java | 2 +- ...emetryQueryingSnmpCommunicationConfig.java | 2 +- ...viceRpcRequestSnmpCommunicationConfig.java | 2 +- .../common/data}/util/CollectionsUtil.java | 24 +- .../common/data/util/ReflectionUtils.java | 2 +- .../server/common/data/util/TbPair.java | 2 +- .../common/data/util/TemplateUtils.java | 56 + .../common/data/util/ThrowingBiFunction.java | 23 + .../data/{sync => util}/ThrowingRunnable.java | 4 +- .../server/common/data/validation/Length.java | 2 +- .../server/common/data/validation/NoXss.java | 2 +- .../common/data/widget/BaseWidgetType.java | 5 +- .../server/common/data/widget/WidgetType.java | 2 +- .../common/data/widget/WidgetTypeDetails.java | 2 +- .../common/data/widget/WidgetTypeInfo.java | 2 +- .../common/data/widget/WidgetsBundle.java | 5 +- .../common/data/DynamicProtoUtilsTest.java | 2 +- .../server/common/data/UUIDConverterTest.java | 9 +- .../server/common/data/id/EntityIdTest.java | 2 +- common/edge-api/pom.xml | 4 +- .../exception/EdgeConnectionException.java | 2 +- .../thingsboard/edge/rpc/EdgeGrpcClient.java | 2 +- .../thingsboard/edge/rpc/EdgeRpcClient.java | 2 +- common/edge-api/src/main/proto/edge.proto | 12 +- common/message/pom.xml | 4 +- .../server/common/msg/EncryptionUtil.java | 2 +- .../server/common/msg/MsgType.java | 2 +- .../server/common/msg/TbActorMsg.java | 2 +- .../server/common/msg/TbActorStopReason.java | 2 +- .../thingsboard/server/common/msg/TbMsg.java | 2 +- .../server/common/msg/TbMsgDataType.java | 2 +- .../server/common/msg/TbMsgMetaData.java | 2 +- .../server/common/msg/TbMsgProcessingCtx.java | 2 +- .../common/msg/TbMsgProcessingStackItem.java | 2 +- .../common/msg/TbRuleEngineActorMsg.java | 2 +- .../msg/ToDeviceActorNotificationMsg.java | 2 +- .../common/msg/aware/CustomerAwareMsg.java | 2 +- .../common/msg/aware/DeviceAwareMsg.java | 2 +- .../server/common/msg/aware/NodeAwareMsg.java | 2 +- .../common/msg/aware/RuleChainAwareMsg.java | 2 +- .../common/msg/aware/TenantAwareMsg.java | 2 +- .../common/msg/cluster/ToAllNodesMsg.java | 2 +- .../common/msg/edge/EdgeEventUpdateMsg.java | 2 +- .../common/msg/edge/EdgeSessionMsg.java | 2 +- .../common/msg/edge/FromEdgeSyncResponse.java | 2 +- .../common/msg/edge/ToEdgeSyncRequest.java | 2 +- .../NotificationRuleProcessor.java | 24 + .../notification/trigger/AlarmTrigger.java | 42 + .../trigger/ApiUsageLimitTrigger.java | 49 + .../trigger/EntitiesLimitTrigger.java | 44 + .../trigger/NewPlatformVersionTrigger.java | 46 + .../trigger/NotificationRuleTrigger.java | 32 + ...eEngineComponentLifecycleEventTrigger.java | 48 + .../trigger/RuleEngineMsgTrigger.java | 46 + .../plugin/ComponentLifecycleListener.java | 2 +- .../msg/plugin/ComponentLifecycleMsg.java | 2 +- .../common/msg/plugin/RuleNodeUpdatedMsg.java | 2 +- .../common/msg/queue/PartitionChangeMsg.java | 2 +- .../msg/queue/QueueToRuleEngineMsg.java | 2 +- .../common/msg/queue/RuleEngineException.java | 2 +- .../common/msg/queue/RuleNodeException.java | 2 +- .../server/common/msg/queue/RuleNodeInfo.java | 2 +- .../server/common/msg/queue/ServiceType.java | 2 +- .../server/common/msg/queue/TbCallback.java | 2 +- .../common/msg/queue/TbMsgCallback.java | 2 +- .../common/msg/queue/TopicPartitionInfo.java | 2 +- .../common/msg/rpc/FromDeviceRpcResponse.java | 2 +- .../common/msg/rpc/ToDeviceRpcRequest.java | 2 +- .../common/msg/session/FeatureType.java | 2 +- .../common/msg/session/SessionMsgType.java | 2 +- .../ex/ProcessingTimeoutException.java | 2 +- .../msg/session/ex/SessionAuthException.java | 2 +- .../msg/session/ex/SessionException.java | 2 +- .../DeviceActorServerSideRpcTimeoutMsg.java | 2 +- .../server/common/msg/timeout/TimeoutMsg.java | 2 +- .../common/msg/tools/SchedulerUtils.java | 2 +- .../server/common/msg/tools/TbRateLimits.java | 2 +- .../msg/tools/TbRateLimitsException.java | 2 +- common/message/src/main/proto/tbmsg.proto | 2 +- .../server/common/msg/TbMsgMetaDataTest.java | 2 +- .../msg/TbMsgProcessingStackItemTest.java | 2 +- .../msg/queue/TopicPartitionInfoTest.java | 2 +- .../common/msg/tools/RateLimitsTest.java | 2 +- common/pom.xml | 4 +- common/queue/pom.xml | 4 +- .../queue/RuleEngineTbQueueAdminFactory.java | 2 +- .../azure/servicebus/TbServiceBusAdmin.java | 2 +- .../TbServiceBusConsumerTemplate.java | 2 +- .../TbServiceBusProducerTemplate.java | 2 +- .../servicebus/TbServiceBusQueueConfigs.java | 2 +- .../servicebus/TbServiceBusSettings.java | 2 +- ...stractParallelTbQueueConsumerTemplate.java | 2 +- .../AbstractTbQueueConsumerTemplate.java | 2 +- .../queue/common/AbstractTbQueueTemplate.java | 2 +- .../queue/common/AsyncCallbackTemplate.java | 2 +- .../queue/common/DefaultTbQueueMsg.java | 2 +- .../common/DefaultTbQueueMsgHeaders.java | 2 +- .../common/DefaultTbQueueRequestTemplate.java | 2 +- .../DefaultTbQueueResponseTemplate.java | 2 +- .../MultipleTbQueueCallbackWrapper.java | 2 +- .../MultipleTbQueueTbMsgCallbackWrapper.java | 2 +- .../queue/common/TbProtoJsQueueMsg.java | 2 +- .../server/queue/common/TbProtoQueueMsg.java | 2 +- .../common/TbQueueTbMsgCallbackWrapper.java | 2 +- .../queue/discovery/ConsistentHashCircle.java | 2 +- .../DefaultTbServiceInfoProvider.java | 41 +- .../queue/discovery/DiscoveryService.java | 10 +- .../discovery/DummyDiscoveryService.java | 13 +- .../queue/discovery/HashPartitionService.java | 18 +- .../discovery/NotificationsTopicService.java | 2 +- .../queue/discovery/PartitionService.java | 5 +- .../server/queue/discovery/QueueKey.java | 8 +- .../queue/discovery/QueueRoutingInfo.java | 2 +- .../discovery/QueueRoutingInfoService.java | 2 +- .../discovery/TbApplicationEventListener.java | 2 +- .../discovery/TbServiceInfoProvider.java | 4 +- .../queue/discovery/TenantRoutingInfo.java | 2 +- .../discovery/TenantRoutingInfoService.java | 2 +- .../queue/discovery/ZkDiscoveryService.java | 34 +- .../event/ClusterTopologyChangeEvent.java | 2 +- .../discovery/event/PartitionChangeEvent.java | 2 +- .../event/ServiceListChangedEvent.java | 2 +- .../discovery/event/TbApplicationEvent.java | 2 +- .../environment/EnvironmentLogService.java | 2 +- .../server/queue/kafka/KafkaTbQueueMsg.java | 2 +- .../queue/kafka/KafkaTbQueueMsgMetadata.java | 2 +- .../server/queue/kafka/TbKafkaAdmin.java | 2 +- .../kafka/TbKafkaConsumerStatisticConfig.java | 2 +- .../kafka/TbKafkaConsumerStatsService.java | 2 +- .../queue/kafka/TbKafkaConsumerTemplate.java | 2 +- .../server/queue/kafka/TbKafkaDecoder.java | 2 +- .../server/queue/kafka/TbKafkaEncoder.java | 2 +- .../queue/kafka/TbKafkaProducerTemplate.java | 2 +- .../server/queue/kafka/TbKafkaSettings.java | 2 +- .../queue/kafka/TbKafkaTopicConfigs.java | 2 +- .../queue/memory/DefaultInMemoryStorage.java | 2 +- .../server/queue/memory/InMemoryStorage.java | 2 +- .../queue/memory/InMemoryTbQueueConsumer.java | 2 +- .../queue/memory/InMemoryTbQueueProducer.java | 2 +- .../RemoteNotificationRuleProcessor.java | 55 + .../provider/AwsSqsMonolithQueueFactory.java | 2 +- .../provider/AwsSqsTbCoreQueueFactory.java | 2 +- .../AwsSqsTbRuleEngineQueueFactory.java | 2 +- .../AwsSqsTbVersionControlQueueFactory.java | 2 +- .../provider/AwsSqsTransportQueueFactory.java | 2 +- .../InMemoryMonolithQueueFactory.java | 2 +- .../InMemoryTbTransportQueueFactory.java | 2 +- .../provider/KafkaMonolithQueueFactory.java | 2 +- .../provider/KafkaTbCoreQueueFactory.java | 2 +- .../KafkaTbRuleEngineQueueFactory.java | 2 +- .../KafkaTbTransportQueueFactory.java | 2 +- .../KafkaTbVersionControlQueueFactory.java | 2 +- .../provider/PubSubMonolithQueueFactory.java | 2 +- .../provider/PubSubTbCoreQueueFactory.java | 2 +- .../PubSubTbRuleEngineQueueFactory.java | 2 +- .../PubSubTbVersionControlQueueFactory.java | 2 +- .../provider/PubSubTransportQueueFactory.java | 2 +- .../RabbitMqMonolithQueueFactory.java | 2 +- .../provider/RabbitMqTbCoreQueueFactory.java | 2 +- .../RabbitMqTbRuleEngineQueueFactory.java | 2 +- .../RabbitMqTbVersionControlQueueFactory.java | 2 +- .../RabbitMqTransportQueueFactory.java | 2 +- .../ServiceBusMonolithQueueFactory.java | 2 +- .../ServiceBusTbCoreQueueFactory.java | 2 +- .../ServiceBusTbRuleEngineQueueFactory.java | 2 +- ...erviceBusTbVersionControlQueueFactory.java | 2 +- .../ServiceBusTransportQueueFactory.java | 2 +- .../queue/provider/TbCoreQueueFactory.java | 2 +- .../provider/TbCoreQueueProducerProvider.java | 2 +- .../provider/TbQueueProducerProvider.java | 2 +- .../TbRuleEngineProducerProvider.java | 2 +- .../provider/TbRuleEngineQueueFactory.java | 2 +- .../provider/TbTransportQueueFactory.java | 2 +- .../TbTransportQueueProducerProvider.java | 2 +- .../TbUsageStatsClientQueueFactory.java | 2 +- .../TbVersionControlProducerProvider.java | 2 +- .../TbVersionControlQueueFactory.java | 2 +- .../server/queue/pubsub/TbPubSubAdmin.java | 2 +- .../pubsub/TbPubSubConsumerTemplate.java | 2 +- .../pubsub/TbPubSubProducerTemplate.java | 2 +- .../server/queue/pubsub/TbPubSubSettings.java | 2 +- .../pubsub/TbPubSubSubscriptionSettings.java | 2 +- .../queue/rabbitmq/TbRabbitMqAdmin.java | 2 +- .../rabbitmq/TbRabbitMqConsumerTemplate.java | 2 +- .../rabbitmq/TbRabbitMqProducerTemplate.java | 2 +- .../rabbitmq/TbRabbitMqQueueArguments.java | 2 +- .../queue/rabbitmq/TbRabbitMqSettings.java | 2 +- .../scheduler/DefaultSchedulerComponent.java | 2 +- .../queue/scheduler/SchedulerComponent.java | 2 +- .../queue/settings/TbQueueCoreSettings.java | 2 +- .../TbQueueRemoteJsInvokeSettings.java | 2 +- .../settings/TbQueueRuleEngineSettings.java | 2 +- .../settings/TbQueueTransportApiSettings.java | 2 +- .../TbQueueTransportNotificationSettings.java | 2 +- .../TbQueueVersionControlSettings.java | 2 +- ...leEngineQueueAckStrategyConfiguration.java | 2 +- .../TbRuleEngineQueueConfiguration.java | 2 +- ...ngineQueueSubmitStrategyConfiguration.java | 2 +- .../queue/sqs/AwsSqsTbQueueMsgMetadata.java | 2 +- .../server/queue/sqs/TbAwsSqsAdmin.java | 2 +- .../queue/sqs/TbAwsSqsConsumerTemplate.java | 2 +- .../queue/sqs/TbAwsSqsProducerTemplate.java | 2 +- .../queue/sqs/TbAwsSqsQueueAttributes.java | 2 +- .../server/queue/sqs/TbAwsSqsSettings.java | 2 +- .../DefaultTbApiUsageReportClient.java | 2 +- .../server/queue/util/AfterContextReady.java | 2 +- .../server/queue/util/AfterStartUp.java | 2 +- .../util/DataDecodingEncodingService.java | 2 +- .../queue/util/ProtoWithFSTService.java | 2 +- .../server/queue/util/TbCoreComponent.java | 2 +- .../TbLwM2mBootstrapTransportComponent.java | 2 +- .../queue/util/TbLwM2mTransportComponent.java | 2 +- .../queue/util/TbRuleEngineComponent.java | 2 +- .../queue/util/TbSnmpTransportComponent.java | 2 +- .../queue/util/TbTransportComponent.java | 2 +- .../queue/util/TbVersionControlComponent.java | 2 +- .../DefaultTbQueueRequestTemplateTest.java | 2 +- .../server/queue/discovery/QueueKeyTest.java | 7 +- .../memory/DefaultInMemoryStorageTest.java | 2 +- common/script/pom.xml | 4 +- common/script/remote-js-client/pom.xml | 4 +- .../service/script/JsExecutorService.java | 2 +- .../service/script/RemoteJsInvokeService.java | 7 +- .../script/RemoteJsRequestEncoder.java | 2 +- .../script/RemoteJsResponseDecoder.java | 2 +- common/script/script-api/pom.xml | 4 +- .../api/AbstractScriptInvokeService.java | 2 +- .../script/api/BlockedScriptInfo.java | 2 +- .../script/api/RuleNodeScriptFactory.java | 2 +- .../script/api/ScriptInvokeService.java | 2 +- .../script/api/ScriptStatCallback.java | 2 +- .../thingsboard/script/api/ScriptType.java | 2 +- .../script/api/TbScriptException.java | 2 +- .../script/api/TbScriptExecutionTask.java | 2 +- .../api/js/AbstractJsInvokeService.java | 2 +- .../script/api/js/JsInvokeService.java | 2 +- .../script/api/js/JsScriptExecutionTask.java | 2 +- .../script/api/js/JsScriptInfo.java | 2 +- .../script/api/js/NashornJsInvokeService.java | 7 +- .../api/tbel/DefaultTbelInvokeService.java | 7 +- .../thingsboard/script/api/tbel/TbDate.java | 2 +- .../thingsboard/script/api/tbel/TbJson.java | 2 +- .../thingsboard/script/api/tbel/TbUtils.java | 2 +- .../script/api/tbel/TbelInvokeService.java | 2 +- .../script/api/tbel/TbelScript.java | 2 +- .../api/tbel/TbelScriptExecutionTask.java | 2 +- .../script/api/tbel/TbUtilsTest.java | 2 +- common/stats/pom.xml | 4 +- .../server/common/stats/DefaultCounter.java | 2 +- .../common/stats/DefaultMessagesStats.java | 2 +- .../common/stats/DefaultStatsFactory.java | 2 +- .../server/common/stats/MessagesStats.java | 2 +- .../server/common/stats/StatsCounter.java | 2 +- .../server/common/stats/StatsFactory.java | 2 +- .../server/common/stats/StatsType.java | 2 +- .../common/stats/TbApiUsageReportClient.java | 2 +- .../common/stats/TbApiUsageStateClient.java | 2 +- common/transport/coap/pom.xml | 4 +- .../coap/AbstractCoapTransportResource.java | 2 +- .../transport/coap/CoapTransportContext.java | 2 +- .../transport/coap/CoapTransportResource.java | 2 +- .../transport/coap/CoapTransportService.java | 2 +- .../coap/OtaPackageTransportResource.java | 2 +- .../transport/coap/TbCoapMessageObserver.java | 2 +- .../coap/TransportConfigurationContainer.java | 2 +- .../coap/adaptors/CoapAdaptorUtils.java | 2 +- .../coap/adaptors/CoapTransportAdaptor.java | 2 +- .../coap/adaptors/JsonCoapAdaptor.java | 2 +- .../coap/adaptors/ProtoCoapAdaptor.java | 2 +- .../callback/AbstractSyncSessionCallback.java | 2 +- .../coap/callback/CoapDeviceAuthCallback.java | 2 +- .../coap/callback/CoapEfentoCallback.java | 2 +- .../coap/callback/CoapNoOpCallback.java | 2 +- .../coap/callback/CoapOkCallback.java | 2 +- .../GetAttributesSyncSessionCallback.java | 2 +- .../ToServerRpcSyncSessionCallback.java | 2 +- .../coap/client/CoapClientContext.java | 2 +- .../coap/client/DefaultCoapClientContext.java | 4 +- .../transport/coap/client/NoSecClient.java | 2 +- .../coap/client/NoSecObserveClient.java | 2 +- .../coap/client/SecureClientNoAuth.java | 2 +- .../coap/client/SecureClientX509.java | 2 +- .../coap/client/TbCoapClientState.java | 2 +- .../coap/client/TbCoapContentFormatUtil.java | 2 +- .../coap/client/TbCoapObservationState.java | 2 +- .../efento/CoapEfentoTransportResource.java | 2 +- .../efento/adaptor/EfentoCoapAdaptor.java | 2 +- .../coap/efento/utils/CoapEfentoUtils.java | 2 +- .../main/proto/proto_measurement_types.proto | 2 +- .../src/main/proto/proto_measurements.proto | 2 +- common/transport/http/pom.xml | 4 +- .../transport/http/DeviceApiController.java | 2 +- .../transport/http/HttpTransportContext.java | 2 +- common/transport/lwm2m/pom.xml | 4 +- .../LwM2MTransportBootstrapService.java | 2 +- .../secure/LwM2MBootstrapConfig.java | 2 +- .../secure/LwM2MBootstrapServers.java | 2 +- .../secure/LwM2MServerBootstrap.java | 2 +- .../LwM2mDefaultBootstrapSessionManager.java | 2 +- ...LwM2MDtlsBootstrapCertificateVerifier.java | 2 +- .../LwM2MBootstrapClientInstanceIds.java | 2 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 2 +- .../store/LwM2MBootstrapSecurityStore.java | 2 +- .../store/LwM2MBootstrapTaskProvider.java | 2 +- .../store/LwM2MConfigurationChecker.java | 2 +- .../LwM2MInMemoryBootstrapConfigStore.java | 2 +- .../lwm2m/config/LwM2MSecureServerConfig.java | 2 +- .../config/LwM2MTransportBootstrapConfig.java | 2 +- .../config/LwM2MTransportServerConfig.java | 2 +- .../lwm2m/config/TbLwM2mVersion.java | 2 +- ...LwM2mCredentialsSecurityInfoValidator.java | 2 +- .../lwm2m/secure/LwM2mRPkCredentials.java | 2 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 2 +- .../TbLwM2MDtlsCertificateVerifier.java | 2 +- .../lwm2m/secure/TbLwM2MSecurityInfo.java | 2 +- .../lwm2m/secure/TbX509DtlsSessionInfo.java | 2 +- .../credentials/LwM2MClientCredentials.java | 2 +- .../AbstractLwM2mTransportResource.java | 2 +- .../server/DefaultLwM2mTransportService.java | 8 +- .../lwm2m/server/LwM2MNetworkConfig.java | 2 +- .../lwm2m/server/LwM2MOperationType.java | 2 +- .../lwm2m/server/LwM2MTransportService.java | 2 +- .../lwm2m/server/LwM2mOtaConvert.java | 2 +- .../lwm2m/server/LwM2mQueuedRequest.java | 2 +- .../lwm2m/server/LwM2mServerListener.java | 2 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 2 +- .../server/LwM2mTransportCoapResource.java | 2 +- .../lwm2m/server/LwM2mTransportContext.java | 2 +- .../server/LwM2mTransportServerHelper.java | 2 +- .../server/LwM2mVersionedModelProvider.java | 2 +- .../server/adaptors/LwM2MJsonAdaptor.java | 2 +- .../adaptors/LwM2MTransportAdaptor.java | 2 +- .../DefaultLwM2MAttributesService.java | 2 +- .../attributes/LwM2MAttributesService.java | 2 +- .../server/client/LwM2MAuthException.java | 2 +- .../lwm2m/server/client/LwM2MClientState.java | 2 +- .../client/LwM2MClientStateException.java | 2 +- .../lwm2m/server/client/LwM2mClient.java | 2 +- .../server/client/LwM2mClientContext.java | 2 +- .../server/client/LwM2mClientContextImpl.java | 6 +- .../lwm2m/server/client/ModelObject.java | 2 +- .../client/ParametersAnalyzeResult.java | 2 +- .../lwm2m/server/client/ResourceValue.java | 2 +- .../client/ResultsAddKeyValueProto.java | 2 +- .../common/LwM2MExecutorAwareService.java | 2 +- .../AbstractTbLwM2MRequestCallback.java | 2 +- ...bstractTbLwM2MTargetedDownlinkRequest.java | 2 +- .../DefaultLwM2mDownlinkMsgHandler.java | 32 +- .../downlink/DownlinkRequestCallback.java | 2 +- .../server/downlink/HasContentFormat.java | 2 +- .../lwm2m/server/downlink/HasVersionedId.java | 2 +- .../server/downlink/HasVersionedIds.java | 2 +- .../downlink/LwM2mDownlinkMsgHandler.java | 2 +- .../TbLwM2MCancelAllObserveCallback.java | 2 +- .../downlink/TbLwM2MCancelAllRequest.java | 2 +- .../TbLwM2MCancelObserveCallback.java | 2 +- .../downlink/TbLwM2MCancelObserveRequest.java | 2 +- .../server/downlink/TbLwM2MCreateRequest.java | 2 +- .../TbLwM2MCreateResponseCallback.java | 2 +- .../downlink/TbLwM2MDeleteCallback.java | 2 +- .../server/downlink/TbLwM2MDeleteRequest.java | 2 +- .../downlink/TbLwM2MDiscoverAllRequest.java | 2 +- .../downlink/TbLwM2MDiscoverCallback.java | 2 +- .../downlink/TbLwM2MDiscoverRequest.java | 2 +- .../downlink/TbLwM2MDownlinkRequest.java | 2 +- .../downlink/TbLwM2MExecuteCallback.java | 2 +- .../downlink/TbLwM2MExecuteRequest.java | 2 +- .../server/downlink/TbLwM2MLatchCallback.java | 2 +- .../downlink/TbLwM2MObserveAllRequest.java | 2 +- .../downlink/TbLwM2MObserveCallback.java | 2 +- .../downlink/TbLwM2MObserveRequest.java | 2 +- .../server/downlink/TbLwM2MReadCallback.java | 2 +- .../server/downlink/TbLwM2MReadRequest.java | 2 +- .../downlink/TbLwM2MTargetedCallback.java | 2 +- .../TbLwM2MUplinkTargetedCallback.java | 2 +- .../TbLwM2MWriteAttributesCallback.java | 2 +- .../TbLwM2MWriteAttributesRequest.java | 2 +- .../downlink/TbLwM2MWriteReplaceRequest.java | 2 +- .../TbLwM2MWriteResponseCallback.java | 2 +- .../downlink/TbLwM2MWriteUpdateRequest.java | 2 +- ...LwM2MTargetedDownlinkCompositeRequest.java | 2 +- .../TbLwM2MReadCompositeCallback.java | 2 +- .../TbLwM2MReadCompositeRequest.java | 2 +- .../TbLwM2MWriteCompositeRequest.java | 2 +- ...TbLwM2MWriteResponseCompositeCallback.java | 2 +- .../log/DefaultLwM2MTelemetryLogService.java | 2 +- .../server/log/LwM2MTelemetryLogService.java | 2 +- .../lwm2m/server/model/LwM2MModelConfig.java | 4 +- .../server/model/LwM2MModelConfigService.java | 2 +- .../model/LwM2MModelConfigServiceImpl.java | 2 +- .../ota/DefaultLwM2MOtaUpdateService.java | 3 +- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 2 +- .../lwm2m/server/ota/LwM2MClientOtaState.java | 2 +- .../server/ota/LwM2MOtaUpdateService.java | 2 +- .../ota/firmware/FirmwareDeliveryMethod.java | 2 +- .../ota/firmware/FirmwareUpdateResult.java | 2 +- .../ota/firmware/FirmwareUpdateState.java | 2 +- .../ota/firmware/LwM2MClientFwOtaInfo.java | 2 +- .../firmware/LwM2MFirmwareUpdateStrategy.java | 2 +- .../ota/software/LwM2MClientSwOtaInfo.java | 2 +- .../software/LwM2MSoftwareUpdateStrategy.java | 2 +- .../ota/software/SoftwareUpdateResult.java | 2 +- .../ota/software/SoftwareUpdateState.java | 2 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 2 +- .../server/rpc/LwM2MRpcRequestHandler.java | 2 +- .../server/rpc/LwM2MRpcRequestHeader.java | 2 +- .../server/rpc/LwM2MRpcResponseBody.java | 2 +- .../rpc/RpcCancelAllObserveCallback.java | 2 +- .../server/rpc/RpcCancelObserveCallback.java | 2 +- .../lwm2m/server/rpc/RpcCreateRequest.java | 2 +- .../server/rpc/RpcCreateResponseCallback.java | 2 +- .../lwm2m/server/rpc/RpcDiscoverCallback.java | 2 +- .../rpc/RpcDownlinkRequestCallbackProxy.java | 4 +- .../server/rpc/RpcEmptyResponseCallback.java | 2 +- .../lwm2m/server/rpc/RpcLinkSetCallback.java | 2 +- .../server/rpc/RpcLwM2MDownlinkCallback.java | 2 +- .../server/rpc/RpcReadResponseCallback.java | 2 +- .../server/rpc/RpcWriteAttributesRequest.java | 2 +- .../server/rpc/RpcWriteReplaceRequest.java | 2 +- .../server/rpc/RpcWriteUpdateRequest.java | 2 +- .../composite/RpcReadCompositeRequest.java | 2 +- .../RpcReadResponseCompositeCallback.java | 2 +- .../composite/RpcWriteCompositeRequest.java | 2 +- .../session/DefaultLwM2MSessionManager.java | 2 +- .../server/session/LwM2MSessionManager.java | 2 +- .../store/TbDummyLwM2MClientOtaInfoStore.java | 2 +- .../server/store/TbDummyLwM2MClientStore.java | 2 +- .../store/TbDummyLwM2MModelConfigStore.java | 2 +- .../server/store/TbEditableSecurityStore.java | 2 +- .../server/store/TbInMemorySecurityStore.java | 2 +- .../TbL2M2MDtlsSessionInMemoryStore.java | 2 +- .../store/TbLwM2MClientOtaInfoStore.java | 2 +- .../server/store/TbLwM2MClientStore.java | 2 +- .../store/TbLwM2MDtlsSessionRedisStore.java | 2 +- .../server/store/TbLwM2MDtlsSessionStore.java | 2 +- .../server/store/TbLwM2MModelConfigStore.java | 2 +- .../store/TbLwM2mRedisClientOtaInfoStore.java | 2 +- .../store/TbLwM2mRedisRegistrationStore.java | 2 +- .../store/TbLwM2mRedisSecurityStore.java | 2 +- .../server/store/TbLwM2mSecurityStore.java | 2 +- .../server/store/TbLwM2mStoreFactory.java | 2 +- .../server/store/TbMainSecurityStore.java | 2 +- .../server/store/TbRedisLwM2MClientStore.java | 2 +- .../store/TbRedisLwM2MModelConfigStore.java | 2 +- .../lwm2m/server/store/TbSecurityStore.java | 2 +- .../server/store/util/LwM2MClientSerDes.java | 2 +- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 52 +- .../lwm2m/server/uplink/LwM2mTypeServer.java | 2 +- .../server/uplink/LwM2mUplinkMsgHandler.java | 8 +- .../lwm2m/utils/LwM2MTransportUtil.java | 18 +- .../lwm2m/utils/LwM2mValueConverterImpl.java | 2 +- .../lwm2m/server/client/LwM2mClientTest.java | 2 +- .../store/util/LwM2MClientSerDesTest.java | 2 +- common/transport/mqtt/pom.xml | 17 +- .../mqtt/MqttSslHandlerProvider.java | 3 +- .../transport/mqtt/MqttTransportContext.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 603 +- .../mqtt/MqttTransportServerInitializer.java | 2 +- .../transport/mqtt/MqttTransportService.java | 2 +- .../server/transport/mqtt/TopicType.java | 2 +- .../BackwardCompatibilityAdaptor.java | 2 +- .../mqtt/adaptors/JsonMqttAdaptor.java | 2 +- .../mqtt/adaptors/MqttTransportAdaptor.java | 2 +- .../mqtt/adaptors/ProtoMqttAdaptor.java | 2 +- .../transport/mqtt/limits/IpFilter.java | 2 +- .../transport/mqtt/limits/ProxyIpFilter.java | 2 +- ... AbstractGatewayDeviceSessionContext.java} | 12 +- .../AbstractGatewaySessionHandler.java | 766 ++ .../mqtt/session/DeviceSessionCtx.java | 21 +- .../session/GatewayDeviceSessionContext.java | 37 + .../mqtt/session/GatewaySessionHandler.java | 706 +- .../MqttDeviceAwareSessionContext.java | 11 +- .../mqtt/session/MqttTopicMatcher.java | 2 +- .../SparkplugDeviceSessionContext.java | 106 + .../session/SparkplugNodeSessionHandler.java | 341 + .../mqtt/util/AlwaysTrueTopicFilter.java | 2 +- .../mqtt/util/EqualsTopicFilter.java | 2 +- .../transport/mqtt/util/MqttTopicFilter.java | 2 +- .../mqtt/util/MqttTopicFilterFactory.java | 2 +- .../transport/mqtt/util/RegexTopicFilter.java | 2 +- .../transport/mqtt/util/ReturnCode.java | 104 + .../mqtt/util/ReturnCodeResolver.java | 61 + .../mqtt/util/sparkplug/MetricDataType.java | 156 + .../sparkplug/SparkplugConnectionState.java | 30 + .../util/sparkplug/SparkplugMessageType.java | 113 + .../util/sparkplug/SparkplugMetricUtil.java | 452 + .../sparkplug/SparkplugRpcRequestHeader.java | 29 + .../sparkplug/SparkplugRpcResponseBody.java | 31 + .../mqtt/util/sparkplug/SparkplugTopic.java | 169 + .../util/sparkplug/SparkplugTopicUtil.java | 116 + .../mqtt/src/main/proto/sparkplug.proto | 204 + .../mqtt/MqttTransportHandlerTest.java | 2 +- .../session/GatewaySessionHandlerTest.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 2 +- common/transport/pom.xml | 4 +- common/transport/snmp/pom.xml | 4 +- .../transport/snmp/SnmpTransportContext.java | 2 +- .../ServiceListChangedEventListener.java | 2 +- .../event/SnmpTransportListChangedEvent.java | 2 +- ...SnmpTransportListChangedEventListener.java | 2 +- .../transport/snmp/service/PduService.java | 2 +- .../service/ProtoTransportEntityService.java | 2 +- .../snmp/service/SnmpAuthService.java | 2 +- .../SnmpTransportBalancingService.java | 2 +- .../snmp/service/SnmpTransportService.java | 2 +- .../snmp/session/DeviceSessionContext.java | 2 +- .../transport/snmp/SnmpDeviceSimulatorV2.java | 2 +- .../transport/snmp/SnmpDeviceSimulatorV3.java | 2 +- .../server/transport/snmp/SnmpTestV2.java | 2 +- .../server/transport/snmp/SnmpTestV3.java | 2 +- common/transport/transport-api/pom.xml | 4 +- .../common/transport/DeviceDeletedEvent.java | 2 +- .../transport/DeviceProfileUpdatedEvent.java | 2 +- .../common/transport/DeviceUpdatedEvent.java | 2 +- .../common/transport/SessionMsgListener.java | 2 +- .../common/transport/TransportAdaptor.java | 2 +- .../common/transport/TransportContext.java | 2 +- .../TransportDeviceProfileCache.java | 2 +- .../transport/TransportResourceCache.java | 2 +- .../common/transport/TransportService.java | 4 +- .../transport/TransportServiceCallback.java | 2 +- .../TransportTenantProfileCache.java | 2 +- .../transport/adaptor/AdaptorException.java | 2 +- .../transport/adaptor/JsonConverter.java | 2 +- .../adaptor/JsonConverterConfig.java | 2 +- .../transport/adaptor/ProtoConverter.java | 2 +- .../transport/auth/DeviceAuthResult.java | 2 +- .../transport/auth/DeviceAuthService.java | 2 +- .../transport/auth/DeviceProfileAware.java | 2 +- .../GetOrCreateDeviceFromGatewayResponse.java | 2 +- .../transport/auth/SessionInfoCreator.java | 2 +- .../transport/auth/TransportDeviceInfo.java | 2 +- .../ValidateDeviceCredentialsResponse.java | 2 +- .../config/ssl/AbstractSslCredentials.java | 2 +- .../config/ssl/KeystoreSslCredentials.java | 2 +- .../config/ssl/PemSslCredentials.java | 2 +- .../transport/config/ssl/SslCredentials.java | 2 +- .../config/ssl/SslCredentialsConfig.java | 2 +- .../config/ssl/SslCredentialsType.java | 2 +- .../SslCredentialsWebServerCustomizer.java | 2 +- .../DefaultTransportRateLimitService.java | 2 +- .../limits/DummyTransportRateLimit.java | 2 +- .../limits/EntityTransportRateLimits.java | 2 +- .../limits/InetAddressRateLimitStats.java | 2 +- .../limits/SimpleTransportRateLimit.java | 2 +- .../transport/limits/TransportRateLimit.java | 2 +- .../limits/TransportRateLimitService.java | 2 +- .../profile/TenantProfileUpdateResult.java | 2 +- .../DefaultTransportDeviceProfileCache.java | 2 +- .../DefaultTransportResourceCache.java | 2 +- .../service/DefaultTransportService.java | 37 +- .../DefaultTransportTenantProfileCache.java | 2 +- .../transport/service/RpcRequestMetadata.java | 2 +- .../service/SessionActivityData.java | 2 +- .../transport/service/SessionMetaData.java | 2 +- .../service/ToRuleEngineMsgEncoder.java | 2 +- .../ToTransportMsgResponseDecoder.java | 2 +- .../service/TransportApiRequestEncoder.java | 2 +- .../service/TransportApiResponseDecoder.java | 2 +- .../TransportQueueRoutingInfoService.java | 2 +- .../TransportTenantRoutingInfoService.java | 2 +- .../session/DeviceAwareSessionContext.java | 16 +- .../transport/session/SessionContext.java | 2 +- .../common/transport/util/JsonUtils.java | 2 +- .../server/common/transport/util/SslUtil.java | 2 +- .../src/main/proto/transport.proto | 2 +- .../src/test/java/JsonConverterTest.java | 26 +- common/util/pom.xml | 8 +- .../util/AbstractListeningExecutor.java | 2 +- .../common/util/AzureIotHubUtil.java | 2 +- .../thingsboard/common/util/DonAsynchron.java | 2 +- .../thingsboard/common/util/JacksonUtil.java | 32 +- .../org/thingsboard/common/util/KvUtil.java | 2 +- .../util/LinkedHashMapRemoveEldest.java | 2 +- .../common/util/ListeningExecutor.java | 2 +- .../thingsboard/common/util/RegexUtils.java | 2 +- .../thingsboard/common/util/SystemUtil.java | 108 + .../thingsboard/common/util/TbStopWatch.java | 2 +- .../common/util/ThingsBoardExecutors.java | 2 +- ...hingsBoardForkJoinWorkerThreadFactory.java | 2 +- .../common/util/ThingsBoardThreadFactory.java | 2 +- .../common/util/JacksonUtilTest.java | 2 +- .../util/LinkedHashMapRemoveEldestTest.java | 2 +- common/version-control/pom.xml | 4 +- .../sync/vc/ClusterVersionControlService.java | 2 +- .../DefaultClusterVersionControlService.java | 4 +- .../sync/vc/DefaultGitRepositoryService.java | 2 +- .../server/service/sync/vc/GitRepository.java | 2 +- .../service/sync/vc/GitRepositoryService.java | 2 +- .../server/service/sync/vc/PendingCommit.java | 2 +- .../sync/vc/VersionControlRequestCtx.java | 2 +- dao/pom.xml | 33 +- .../java/org/thingsboard/server/dao/Dao.java | 2 +- .../org/thingsboard/server/dao/DaoUtil.java | 9 +- .../server/dao/ExportableEntityDao.java | 2 +- .../dao/ExportableEntityRepository.java | 2 +- .../thingsboard/server/dao/JpaDaoConfig.java | 2 +- .../server/dao/SqlTimeseriesDaoConfig.java | 2 +- .../server/dao/SqlTsDaoConfig.java | 2 +- .../server/dao/SqlTsLatestDaoConfig.java | 2 +- .../server/dao/TenantEntityDao.java | 2 +- .../server/dao/TenantEntityWithDataDao.java | 2 +- .../server/dao/TimescaleDaoConfig.java | 2 +- .../dao/TimescaleTsLatestDaoConfig.java | 2 +- .../server/dao/alarm/AlarmCommentDao.java | 41 + .../server/dao/alarm/AlarmDao.java | 31 +- .../dao/alarm/BaseAlarmCommentService.java | 120 + .../server/dao/alarm/BaseAlarmService.java | 296 +- .../server/dao/aspect/DbCallStats.java | 2 +- .../dao/aspect/DbCallStatsSnapshot.java | 2 +- .../server/dao/aspect/MethodCallStats.java | 2 +- .../dao/aspect/MethodCallStatsSnapshot.java | 2 +- .../server/dao/aspect/SqlDaoCallsAspect.java | 2 +- .../dao/asset/AssetCacheEvictEvent.java | 2 +- .../server/dao/asset/AssetCacheKey.java | 2 +- .../server/dao/asset/AssetCaffeineCache.java | 2 +- .../server/dao/asset/AssetDao.java | 2 +- .../dao/asset/AssetProfileCacheKey.java | 2 +- .../dao/asset/AssetProfileCaffeineCache.java | 2 +- .../server/dao/asset/AssetProfileDao.java | 2 +- .../dao/asset/AssetProfileEvictEvent.java | 2 +- .../dao/asset/AssetProfileRedisCache.java | 2 +- .../dao/asset/AssetProfileServiceImpl.java | 20 +- .../server/dao/asset/AssetRedisCache.java | 2 +- .../server/dao/asset/AssetTypeFilter.java | 2 +- .../server/dao/asset/BaseAssetService.java | 30 +- .../dao/attributes/AttributeCacheKey.java | 2 +- .../attributes/AttributeCaffeineCache.java | 2 +- .../dao/attributes/AttributeRedisCache.java | 2 +- .../server/dao/attributes/AttributeUtils.java | 3 +- .../server/dao/attributes/AttributesDao.java | 2 +- .../dao/attributes/BaseAttributesService.java | 2 +- .../attributes/CachedAttributesService.java | 8 +- .../server/dao/audit/AuditLogDao.java | 2 +- .../server/dao/audit/AuditLogLevelFilter.java | 2 +- .../server/dao/audit/AuditLogLevelMask.java | 2 +- .../dao/audit/AuditLogLevelProperties.java | 2 +- .../server/dao/audit/AuditLogServiceImpl.java | 17 +- .../dao/audit/DummyAuditLogServiceImpl.java | 3 +- .../server/dao/audit/sink/AuditLogSink.java | 2 +- .../dao/audit/sink/DummyAuditLogSink.java | 2 +- .../audit/sink/ElasticsearchAuditLogSink.java | 2 +- .../dao/cache/CacheExecutorService.java | 2 +- .../BaseComponentDescriptorService.java | 2 +- .../dao/component/ComponentDescriptorDao.java | 2 +- .../server/dao/customer/CustomerDao.java | 2 +- .../dao/customer/CustomerServiceImpl.java | 35 +- .../server/dao/dashboard/DashboardDao.java | 2 +- .../dao/dashboard/DashboardInfoDao.java | 5 +- .../dao/dashboard/DashboardServiceImpl.java | 72 +- .../dashboard/DashboardTitleEvictEvent.java | 25 + .../DashboardTitlesCaffeineCache.java | 33 + .../dashboard/DashboardTitlesRedisCache.java | 35 + .../server/dao/device/ClaimDataInfo.java | 2 +- .../DeviceCredentialsCaffeineCache.java | 2 +- .../dao/device/DeviceCredentialsDao.java | 2 +- .../device/DeviceCredentialsEvictEvent.java | 2 +- .../device/DeviceCredentialsRedisCache.java | 2 +- .../device/DeviceCredentialsServiceImpl.java | 2 +- .../server/dao/device/DeviceDao.java | 2 +- .../dao/device/DeviceProfileCacheKey.java | 2 +- .../device/DeviceProfileCaffeineCache.java | 2 +- .../server/dao/device/DeviceProfileDao.java | 2 +- .../dao/device/DeviceProfileEvictEvent.java | 2 +- .../dao/device/DeviceProfileRedisCache.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 20 +- .../server/dao/device/DeviceServiceImpl.java | 27 +- .../server/dao/edge/BaseEdgeEventService.java | 2 +- .../server/dao/edge/EdgeCacheEvictEvent.java | 2 +- .../server/dao/edge/EdgeCacheKey.java | 2 +- .../server/dao/edge/EdgeCaffeineCache.java | 2 +- .../thingsboard/server/dao/edge/EdgeDao.java | 2 +- .../server/dao/edge/EdgeEventDao.java | 2 +- .../server/dao/edge/EdgeRedisCache.java | 2 +- .../server/dao/edge/EdgeServiceImpl.java | 16 +- .../entity/AbstractCachedEntityService.java | 2 +- .../dao/entity/AbstractCachedService.java | 43 + .../dao/entity/AbstractEntityService.java | 2 +- .../dao/entity/BaseEntityCountService.java | 49 + .../server/dao/entity/BaseEntityService.java | 221 +- .../entity/DefaultEntityServiceRegistry.java | 58 + .../entity/EntityCountCacheEvictEvent.java | 28 + .../dao/entity/EntityCountCacheKey.java | 41 + .../server/dao/entity/EntityQueryDao.java | 2 +- .../count/EntityCountCaffeineCache.java | 33 + .../entity/count/EntityCountRedisCache.java | 35 + .../dao/entityview/EntityViewCacheKey.java | 2 +- .../dao/entityview/EntityViewCacheValue.java | 2 +- .../entityview/EntityViewCaffeineCache.java | 2 +- .../server/dao/entityview/EntityViewDao.java | 2 +- .../dao/entityview/EntityViewEvictEvent.java | 2 +- .../dao/entityview/EntityViewRedisCache.java | 2 +- .../dao/entityview/EntityViewServiceImpl.java | 17 +- .../server/dao/event/BaseEventService.java | 3 +- .../server/dao/event/EventDao.java | 2 +- .../dao/exception/BufferLimitException.java | 2 +- .../exception/DataValidationException.java | 2 +- .../dao/exception/DatabaseException.java | 2 +- .../DeviceCredentialsValidationException.java | 2 +- .../IncorrectParameterException.java | 2 +- .../server/dao/model/BaseEntity.java | 2 +- .../server/dao/model/BaseSqlEntity.java | 76 +- .../server/dao/model/ModelConstants.java | 78 +- .../server/dao/model/SearchTextEntity.java | 2 +- .../thingsboard/server/dao/model/ToData.java | 2 +- .../model/sql/AbstractAlarmCommentEntity.java | 98 + .../dao/model/sql/AbstractAlarmEntity.java | 47 +- .../dao/model/sql/AbstractAssetEntity.java | 2 +- .../dao/model/sql/AbstractDeviceEntity.java | 2 +- .../dao/model/sql/AbstractEdgeEntity.java | 2 +- .../model/sql/AbstractEntityViewEntity.java | 2 +- .../dao/model/sql/AbstractTenantEntity.java | 2 +- .../dao/model/sql/AbstractTsKvEntity.java | 2 +- .../model/sql/AbstractWidgetTypeEntity.java | 2 +- .../dao/model/sql/AdminSettingsEntity.java | 2 +- .../dao/model/sql/AlarmCommentEntity.java | 55 + .../dao/model/sql/AlarmCommentInfoEntity.java | 50 + .../server/dao/model/sql/AlarmEntity.java | 2 +- .../server/dao/model/sql/AlarmInfoEntity.java | 51 +- .../dao/model/sql/ApiUsageStateEntity.java | 2 +- .../server/dao/model/sql/AssetEntity.java | 2 +- .../server/dao/model/sql/AssetInfoEntity.java | 2 +- .../dao/model/sql/AssetProfileEntity.java | 11 +- .../model/sql/AttributeKvCompositeKey.java | 2 +- .../dao/model/sql/AttributeKvEntity.java | 2 +- .../server/dao/model/sql/AuditLogEntity.java | 2 +- .../model/sql/ComponentDescriptorEntity.java | 2 +- .../server/dao/model/sql/CustomerEntity.java | 2 +- .../server/dao/model/sql/DashboardEntity.java | 2 +- .../dao/model/sql/DashboardInfoEntity.java | 2 +- .../model/sql/DeviceCredentialsEntity.java | 2 +- .../server/dao/model/sql/DeviceEntity.java | 2 +- .../dao/model/sql/DeviceInfoEntity.java | 2 +- .../dao/model/sql/DeviceProfileEntity.java | 11 +- .../server/dao/model/sql/EdgeEntity.java | 2 +- .../server/dao/model/sql/EdgeEventEntity.java | 2 +- .../server/dao/model/sql/EdgeInfoEntity.java | 2 +- .../model/sql/EntityAlarmCompositeKey.java | 2 +- .../dao/model/sql/EntityAlarmEntity.java | 4 +- .../dao/model/sql/EntityViewEntity.java | 2 +- .../dao/model/sql/EntityViewInfoEntity.java | 2 +- .../dao/model/sql/ErrorEventEntity.java | 2 +- .../server/dao/model/sql/EventEntity.java | 2 +- .../dao/model/sql/LifecycleEventEntity.java | 2 +- .../dao/model/sql/NotificationEntity.java | 108 + .../model/sql/NotificationRequestEntity.java | 148 + .../sql/NotificationRequestInfoEntity.java | 72 + .../dao/model/sql/NotificationRuleEntity.java | 113 + .../model/sql/NotificationRuleInfoEntity.java | 53 + .../model/sql/NotificationTargetEntity.java | 73 + .../model/sql/NotificationTemplateEntity.java | 82 + ...Auth2ClientRegistrationTemplateEntity.java | 2 +- .../dao/model/sql/OAuth2DomainEntity.java | 2 +- .../dao/model/sql/OAuth2MobileEntity.java | 2 +- .../dao/model/sql/OAuth2ParamsEntity.java | 2 +- .../model/sql/OAuth2RegistrationEntity.java | 2 +- .../dao/model/sql/OtaPackageEntity.java | 2 +- .../dao/model/sql/OtaPackageInfoEntity.java | 2 +- .../server/dao/model/sql/QueueEntity.java | 2 +- .../dao/model/sql/RelationCompositeKey.java | 2 +- .../server/dao/model/sql/RelationEntity.java | 2 +- .../server/dao/model/sql/RpcEntity.java | 2 +- .../model/sql/RuleChainDebugEventEntity.java | 2 +- .../server/dao/model/sql/RuleChainEntity.java | 2 +- .../model/sql/RuleNodeDebugEventEntity.java | 2 +- .../server/dao/model/sql/RuleNodeEntity.java | 2 +- .../dao/model/sql/RuleNodeStateEntity.java | 2 +- .../dao/model/sql/StatisticsEventEntity.java | 2 +- .../dao/model/sql/TbResourceEntity.java | 2 +- .../dao/model/sql/TbResourceInfoEntity.java | 2 +- .../server/dao/model/sql/TenantEntity.java | 2 +- .../dao/model/sql/TenantInfoEntity.java | 2 +- .../dao/model/sql/TenantProfileEntity.java | 2 +- .../dao/model/sql/UserAuthSettingsEntity.java | 2 +- .../dao/model/sql/UserCredentialsEntity.java | 10 +- .../server/dao/model/sql/UserEntity.java | 7 +- .../dao/model/sql/UserSettingsEntity.java | 75 + .../model/sql/WidgetTypeDetailsEntity.java | 2 +- .../dao/model/sql/WidgetTypeEntity.java | 2 +- .../dao/model/sql/WidgetTypeInfoEntity.java | 2 +- .../dao/model/sql/WidgetsBundleEntity.java | 2 +- .../sqlts/dictionary/TsKvDictionary.java | 2 +- .../TsKvDictionaryCompositeKey.java | 2 +- .../sqlts/latest/TsKvLatestCompositeKey.java | 2 +- .../model/sqlts/latest/TsKvLatestEntity.java | 2 +- .../ts/TimescaleTsKvCompositeKey.java | 2 +- .../timescale/ts/TimescaleTsKvEntity.java | 2 +- .../dao/model/sqlts/ts/TsKvCompositeKey.java | 2 +- .../server/dao/model/sqlts/ts/TsKvEntity.java | 2 +- .../dao/nosql/CassandraAbstractAsyncDao.java | 2 +- .../dao/nosql/CassandraAbstractDao.java | 4 +- .../CassandraBufferedRateReadExecutor.java | 2 +- .../CassandraBufferedRateWriteExecutor.java | 2 +- .../DefaultNotificationRequestService.java | 120 + .../DefaultNotificationRuleService.java | 142 + .../DefaultNotificationService.java | 100 + .../DefaultNotificationSettingsService.java | 330 + .../DefaultNotificationTargetService.java | 205 + .../DefaultNotificationTemplateService.java | 96 + .../dao/notification/NotificationDao.java | 44 + .../notification/NotificationRequestDao.java | 59 + .../dao/notification/NotificationRuleDao.java | 44 + .../notification/NotificationTargetDao.java | 38 + .../notification/NotificationTemplateDao.java | 33 + .../cache/NotificationRuleCacheKey.java | 43 + .../cache/NotificationRuleCacheValue.java | 37 + .../cache/NotificationRuleCaffeineCache.java | 32 + .../cache/NotificationRuleRedisCache.java | 35 + .../HybridClientRegistrationRepository.java | 2 +- .../OAuth2ClientRegistrationTemplateDao.java | 2 +- .../OAuth2ConfigTemplateServiceImpl.java | 2 +- .../dao/oauth2/OAuth2Configuration.java | 2 +- .../server/dao/oauth2/OAuth2DomainDao.java | 2 +- .../server/dao/oauth2/OAuth2MobileDao.java | 2 +- .../server/dao/oauth2/OAuth2ParamsDao.java | 2 +- .../dao/oauth2/OAuth2RegistrationDao.java | 2 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 2 +- .../server/dao/oauth2/OAuth2Utils.java | 2 +- .../server/dao/ota/BaseOtaPackageService.java | 17 +- .../dao/ota/OtaPackageCacheEvictEvent.java | 2 +- .../server/dao/ota/OtaPackageCacheKey.java | 2 +- .../dao/ota/OtaPackageCaffeineCache.java | 2 +- .../server/dao/ota/OtaPackageDao.java | 2 +- .../server/dao/ota/OtaPackageInfoDao.java | 2 +- .../server/dao/ota/OtaPackageRedisCache.java | 2 +- .../server/dao/queue/BaseQueueService.java | 18 +- .../server/dao/queue/QueueDao.java | 2 +- .../dao/relation/BaseRelationService.java | 13 +- .../dao/relation/EntityRelationEvent.java | 2 +- .../server/dao/relation/RelationCacheKey.java | 2 +- .../dao/relation/RelationCacheValue.java | 2 +- .../dao/relation/RelationCaffeineCache.java | 2 +- .../server/dao/relation/RelationDao.java | 2 +- .../dao/relation/RelationRedisCache.java | 2 +- .../dao/resource/BaseResourceService.java | 17 +- .../server/dao/resource/TbResourceDao.java | 2 +- .../dao/resource/TbResourceInfoDao.java | 2 +- .../server/dao/rpc/BaseRpcService.java | 19 +- .../thingsboard/server/dao/rpc/RpcDao.java | 2 +- .../server/dao/rule/BaseRuleChainService.java | 19 +- .../dao/rule/BaseRuleNodeStateService.java | 2 +- .../server/dao/rule/RuleChainDao.java | 2 +- .../server/dao/rule/RuleNodeDao.java | 2 +- .../server/dao/rule/RuleNodeStateDao.java | 2 +- .../dao/service/ConstraintValidator.java | 29 +- .../server/dao/service/DataValidator.java | 21 +- .../server/dao/service/NoXssValidator.java | 3 +- .../server/dao/service/PaginatedRemover.java | 2 +- .../dao/service/StringLengthValidator.java | 17 +- .../dao/service/TimePaginatedRemover.java | 2 +- .../server/dao/service/Validator.java | 2 +- .../AbstractHasOtaPackageValidator.java | 2 +- .../validator/AdminSettingsDataValidator.java | 6 +- .../validator/AlarmCommentDataValidator.java | 38 + .../service/validator/AlarmDataValidator.java | 2 +- .../validator/ApiUsageDataValidator.java | 2 +- .../service/validator/AssetDataValidator.java | 13 +- .../validator/AssetProfileDataValidator.java | 2 +- .../validator/AuditLogDataValidator.java | 2 +- .../BaseOtaPackageDataValidator.java | 2 +- ...ientRegistrationTemplateDataValidator.java | 2 +- .../ComponentDescriptorDataValidator.java | 2 +- .../validator/CustomerDataValidator.java | 15 +- .../validator/DashboardDataValidator.java | 14 +- .../DeviceCredentialsDataValidator.java | 2 +- .../validator/DeviceDataValidator.java | 14 +- .../validator/DeviceProfileDataValidator.java | 2 +- .../service/validator/EdgeDataValidator.java | 2 +- .../validator/EdgeEventDataValidator.java | 2 +- .../validator/EntityViewDataValidator.java | 2 +- .../service/validator/EventDataValidator.java | 2 +- .../validator/OtaPackageDataValidator.java | 2 +- .../OtaPackageInfoDataValidator.java | 2 +- .../dao/service/validator/QueueValidator.java | 2 +- .../validator/ResourceDataValidator.java | 2 +- .../validator/RuleChainDataValidator.java | 11 +- .../validator/TenantDataValidator.java | 2 +- .../validator/TenantProfileDataValidator.java | 5 +- .../UserCredentialsDataValidator.java | 2 +- .../service/validator/UserDataValidator.java | 14 +- .../validator/WidgetTypeDataValidator.java | 2 +- .../validator/WidgetsBundleDataValidator.java | 2 +- .../server/dao/settings/AdminSettingsDao.java | 2 +- .../settings/AdminSettingsServiceImpl.java | 2 +- .../server/dao/sql/JpaAbstractDao.java | 2 +- ...paAbstractDaoListeningExecutorService.java | 2 +- .../dao/sql/JpaAbstractSearchTextDao.java | 2 +- .../server/dao/sql/JpaExecutorService.java | 2 +- .../sql/ScheduledLogExecutorComponent.java | 2 +- .../server/dao/sql/TbSqlBlockingQueue.java | 2 +- .../dao/sql/TbSqlBlockingQueueParams.java | 2 +- .../dao/sql/TbSqlBlockingQueueWrapper.java | 2 +- .../server/dao/sql/TbSqlQueue.java | 2 +- .../server/dao/sql/TbSqlQueueElement.java | 2 +- .../dao/sql/alarm/AlarmCommentRepository.java | 39 + .../server/dao/sql/alarm/AlarmRepository.java | 107 +- .../dao/sql/alarm/EntityAlarmRepository.java | 2 +- .../dao/sql/alarm/JpaAlarmCommentDao.java | 99 + .../server/dao/sql/alarm/JpaAlarmDao.java | 252 +- .../dao/sql/asset/AssetProfileRepository.java | 8 +- .../server/dao/sql/asset/AssetRepository.java | 2 +- .../server/dao/sql/asset/JpaAssetDao.java | 2 +- .../dao/sql/asset/JpaAssetProfileDao.java | 2 +- .../AttributeKvInsertRepository.java | 2 +- .../sql/attributes/AttributeKvRepository.java | 2 +- .../dao/sql/attributes/JpaAttributeDao.java | 2 +- .../SqlAttributesInsertRepository.java | 2 +- .../dao/sql/audit/AuditLogRepository.java | 2 +- .../server/dao/sql/audit/JpaAuditLogDao.java | 2 +- ...ctComponentDescriptorInsertRepository.java | 2 +- .../ComponentDescriptorInsertRepository.java | 2 +- .../ComponentDescriptorRepository.java | 2 +- .../JpaBaseComponentDescriptorDao.java | 2 +- ...qlComponentDescriptorInsertRepository.java | 2 +- .../dao/sql/customer/CustomerRepository.java | 2 +- .../dao/sql/customer/JpaCustomerDao.java | 2 +- .../dashboard/DashboardInfoRepository.java | 4 +- .../sql/dashboard/DashboardRepository.java | 2 +- .../dao/sql/dashboard/JpaDashboardDao.java | 2 +- .../sql/dashboard/JpaDashboardInfoDao.java | 7 +- .../device/DefaultNativeDeviceRepository.java | 2 +- .../device/DeviceCredentialsRepository.java | 2 +- .../sql/device/DeviceProfileRepository.java | 10 +- .../dao/sql/device/DeviceRepository.java | 2 +- .../sql/device/JpaDeviceCredentialsDao.java | 2 +- .../server/dao/sql/device/JpaDeviceDao.java | 2 +- .../dao/sql/device/JpaDeviceProfileDao.java | 2 +- .../sql/device/NativeDeviceRepository.java | 2 +- .../sql/edge/EdgeEventInsertRepository.java | 2 +- .../dao/sql/edge/EdgeEventRepository.java | 2 +- .../server/dao/sql/edge/EdgeRepository.java | 2 +- .../dao/sql/edge/JpaBaseEdgeEventDao.java | 2 +- .../server/dao/sql/edge/JpaEdgeDao.java | 2 +- .../sql/entityview/EntityViewRepository.java | 2 +- .../dao/sql/entityview/JpaEntityViewDao.java | 2 +- .../dao/sql/event/ErrorEventRepository.java | 2 +- .../dao/sql/event/EventCleanupRepository.java | 2 +- .../dao/sql/event/EventInsertRepository.java | 2 +- .../event/EventPartitionConfiguration.java | 2 +- .../server/dao/sql/event/EventRepository.java | 2 +- .../server/dao/sql/event/JpaBaseEventDao.java | 2 +- .../sql/event/LifecycleEventRepository.java | 2 +- .../event/RuleChainDebugEventRepository.java | 2 +- .../event/RuleNodeDebugEventRepository.java | 2 +- .../sql/event/SqlEventCleanupRepository.java | 2 +- .../sql/event/StatisticsEventRepository.java | 2 +- .../sql/notification/JpaNotificationDao.java | 119 + .../JpaNotificationRequestDao.java | 132 + .../notification/JpaNotificationRuleDao.java | 100 + .../JpaNotificationTargetDao.java | 89 + .../JpaNotificationTemplateDao.java | 72 + .../notification/NotificationRepository.java | 71 + .../NotificationRequestRepository.java | 78 + .../NotificationRuleRepository.java | 60 + .../NotificationTargetRepository.java | 53 + .../NotificationTemplateRepository.java | 46 + ...paOAuth2ClientRegistrationTemplateDao.java | 2 +- .../dao/sql/oauth2/JpaOAuth2DomainDao.java | 2 +- .../dao/sql/oauth2/JpaOAuth2MobileDao.java | 2 +- .../dao/sql/oauth2/JpaOAuth2ParamsDao.java | 2 +- .../sql/oauth2/JpaOAuth2RegistrationDao.java | 2 +- ...2ClientRegistrationTemplateRepository.java | 2 +- .../sql/oauth2/OAuth2DomainRepository.java | 2 +- .../sql/oauth2/OAuth2MobileRepository.java | 2 +- .../sql/oauth2/OAuth2ParamsRepository.java | 2 +- .../oauth2/OAuth2RegistrationRepository.java | 2 +- .../server/dao/sql/ota/JpaOtaPackageDao.java | 2 +- .../dao/sql/ota/JpaOtaPackageInfoDao.java | 2 +- .../dao/sql/ota/OtaPackageInfoRepository.java | 2 +- .../dao/sql/ota/OtaPackageRepository.java | 2 +- .../dao/sql/query/AlarmDataAdapter.java | 30 +- .../dao/sql/query/AlarmQueryRepository.java | 6 +- .../query/DefaultAlarmQueryRepository.java | 228 +- .../query/DefaultEntityQueryRepository.java | 18 +- .../sql/query/DefaultQueryLogComponent.java | 2 +- .../dao/sql/query/EntityDataAdapter.java | 17 +- .../dao/sql/query/EntityKeyMapping.java | 4 +- .../dao/sql/query/EntityQueryRepository.java | 2 +- .../dao/sql/query/JpaEntityQueryDao.java | 2 +- .../server/dao/sql/query/QueryContext.java | 2 +- .../dao/sql/query/QueryLogComponent.java | 2 +- .../dao/sql/query/QuerySecurityContext.java | 2 +- .../server/dao/sql/queue/JpaQueueDao.java | 5 +- .../server/dao/sql/queue/QueueRepository.java | 2 +- .../dao/sql/relation/JpaRelationDao.java | 2 +- .../JpaRelationQueryExecutorService.java | 2 +- .../relation/RelationInsertRepository.java | 2 +- .../dao/sql/relation/RelationRepository.java | 2 +- .../relation/SqlRelationInsertRepository.java | 2 +- .../dao/sql/resource/JpaTbResourceDao.java | 2 +- .../sql/resource/JpaTbResourceInfoDao.java | 2 +- .../resource/TbResourceInfoRepository.java | 2 +- .../sql/resource/TbResourceRepository.java | 2 +- .../server/dao/sql/rpc/JpaRpcDao.java | 2 +- .../server/dao/sql/rpc/RpcRepository.java | 2 +- .../server/dao/sql/rule/JpaRuleChainDao.java | 2 +- .../server/dao/sql/rule/JpaRuleNodeDao.java | 2 +- .../dao/sql/rule/JpaRuleNodeStateDao.java | 2 +- .../dao/sql/rule/RuleChainRepository.java | 2 +- .../dao/sql/rule/RuleNodeRepository.java | 2 +- .../dao/sql/rule/RuleNodeStateRepository.java | 2 +- .../sql/settings/AdminSettingsRepository.java | 2 +- .../dao/sql/settings/JpaAdminSettingsDao.java | 2 +- .../server/dao/sql/tenant/JpaTenantDao.java | 2 +- .../dao/sql/tenant/JpaTenantProfileDao.java | 10 +- .../sql/tenant/TenantProfileRepository.java | 5 +- .../dao/sql/tenant/TenantRepository.java | 2 +- .../usagerecord/ApiUsageStateRepository.java | 2 +- .../sql/usagerecord/JpaApiUsageStateDao.java | 2 +- .../dao/sql/user/JpaUserAuthSettingsDao.java | 2 +- .../dao/sql/user/JpaUserCredentialsDao.java | 2 +- .../server/dao/sql/user/JpaUserDao.java | 37 +- .../dao/sql/user/JpaUserSettingsDao.java | 53 + .../sql/user/UserAuthSettingsRepository.java | 2 +- .../sql/user/UserCredentialsRepository.java | 2 +- .../server/dao/sql/user/UserRepository.java | 23 +- .../dao/sql/user/UserSettingsRepository.java | 26 + .../dao/sql/widget/JpaWidgetTypeDao.java | 2 +- .../dao/sql/widget/JpaWidgetsBundleDao.java | 2 +- .../dao/sql/widget/WidgetTypeRepository.java | 2 +- .../sql/widget/WidgetsBundleRepository.java | 2 +- ...stractChunkedAggregationTimeseriesDao.java | 2 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 2 +- .../dao/sqlts/AggregationTimeseriesDao.java | 2 +- .../sqlts/BaseAbstractSqlTimeseriesDao.java | 2 +- .../server/dao/sqlts/EntityContainer.java | 2 +- .../dao/sqlts/SqlTimeseriesLatestDao.java | 4 +- .../thingsboard/server/dao/sqlts/TsKey.java | 2 +- .../dictionary/TsKvDictionaryRepository.java | 2 +- .../insert/AbstractInsertRepository.java | 2 +- .../dao/sqlts/insert/InsertTsRepository.java | 2 +- .../latest/InsertLatestTsRepository.java | 2 +- .../sql/SqlLatestInsertTsRepository.java | 2 +- .../insert/sql/SqlInsertTsRepository.java | 2 +- .../insert/sql/SqlPartitioningRepository.java | 17 +- .../TimescaleInsertTsRepository.java | 2 +- .../latest/SearchTsKvLatestRepository.java | 2 +- .../sqlts/latest/TsKvLatestRepository.java | 2 +- .../dao/sqlts/sql/JpaSqlTimeseriesDao.java | 8 +- .../timescale/AggregationRepository.java | 2 +- .../timescale/TimescaleTimeseriesDao.java | 2 +- .../timescale/TsKvTimescaleRepository.java | 2 +- .../server/dao/sqlts/ts/TsKvRepository.java | 2 +- .../tenant/DefaultTbTenantProfileCache.java | 2 +- .../dao/tenant/TenantCaffeineCache.java | 2 +- .../server/dao/tenant/TenantDao.java | 2 +- .../server/dao/tenant/TenantEvictEvent.java | 2 +- .../dao/tenant/TenantExistsCaffeineCache.java | 2 +- .../dao/tenant/TenantExistsRedisCache.java | 2 +- .../dao/tenant/TenantProfileCacheKey.java | 2 +- .../tenant/TenantProfileCaffeineCache.java | 2 +- .../server/dao/tenant/TenantProfileDao.java | 5 +- .../dao/tenant/TenantProfileEvictEvent.java | 2 +- .../dao/tenant/TenantProfileRedisCache.java | 2 +- .../dao/tenant/TenantProfileServiceImpl.java | 24 +- .../server/dao/tenant/TenantRedisCache.java | 2 +- .../server/dao/tenant/TenantServiceImpl.java | 48 +- .../AbstractCassandraBaseTimeseriesDao.java | 2 +- .../AggregatePartitionsFunction.java | 2 +- .../dao/timeseries/BaseTimeseriesService.java | 2 +- .../CassandraBaseTimeseriesDao.java | 2 +- .../CassandraBaseTimeseriesLatestDao.java | 2 +- .../CassandraPartitionCacheKey.java | 2 +- .../CassandraTsPartitionsCache.java | 2 +- .../dao/timeseries/NoSqlTsPartitionDate.java | 2 +- .../server/dao/timeseries/QueryCursor.java | 2 +- .../timeseries/SimpleListenableFuture.java | 2 +- .../server/dao/timeseries/SqlPartition.java | 2 +- .../dao/timeseries/SqlTsPartitionDate.java | 2 +- .../server/dao/timeseries/TimeseriesDao.java | 2 +- .../dao/timeseries/TimeseriesLatestDao.java | 2 +- .../dao/timeseries/TsInsertExecutorType.java | 2 +- .../dao/timeseries/TsKvQueryCursor.java | 2 +- .../dao/usage/BasicUsageInfoService.java | 98 + .../dao/usagerecord/ApiUsageStateDao.java | 2 +- .../usagerecord/ApiUsageStateServiceImpl.java | 16 +- .../usagerecord/DefaultApiLimitService.java | 64 + .../server/dao/user/UserAuthSettingsDao.java | 2 +- .../server/dao/user/UserCredentialsDao.java | 2 +- .../thingsboard/server/dao/user/UserDao.java | 24 +- .../server/dao/user/UserServiceImpl.java | 102 +- .../dao/user/UserSettingsCaffeineCache.java | 35 + .../server/dao/user/UserSettingsDao.java | 31 + .../dao/user/UserSettingsEvictEvent.java | 25 + .../dao/user/UserSettingsRedisCache.java | 37 + .../dao/user/UserSettingsServiceImpl.java | 157 + .../util/AbstractBufferedRateExecutor.java | 8 +- .../server/dao/util/AsyncRateLimiter.java | 2 +- .../server/dao/util/AsyncTaskContext.java | 2 +- .../server/dao/util/BufferedRateExecutor.java | 2 +- .../dao/util/BufferedRateExecutorStats.java | 2 +- .../dao/util/TenantRateLimitException.java | 2 +- .../AbstractJsonSqlTypeDescriptor.java | 2 +- .../mapping/JsonBinarySqlTypeDescriptor.java | 2 +- .../dao/util/mapping/JsonBinaryType.java | 2 +- .../mapping/JsonStringSqlTypeDescriptor.java | 2 +- .../dao/util/mapping/JsonStringType.java | 2 +- .../dao/util/mapping/JsonTypeDescriptor.java | 2 +- .../server/dao/widget/WidgetTypeDao.java | 2 +- .../dao/widget/WidgetTypeServiceImpl.java | 18 +- .../server/dao/widget/WidgetsBundleDao.java | 2 +- .../dao/widget/WidgetsBundleServiceImpl.java | 18 +- .../resources/cassandra/schema-keyspace.cql | 2 +- .../resources/cassandra/schema-ts-latest.cql | 4 +- .../main/resources/cassandra/schema-ts.cql | 6 +- .../sql/schema-entities-idx-psql-addon.sql | 2 +- .../resources/sql/schema-entities-idx.sql | 33 +- .../main/resources/sql/schema-entities.sql | 348 +- .../main/resources/sql/schema-timescale.sql | 2 +- dao/src/main/resources/sql/schema-ts-psql.sql | 26 +- dao/src/main/resources/xss-policy.xml | 2 +- .../cassandra/io/sstable/Descriptor.java | 366 - .../io/sstable/format/SSTableFormat.java | 86 - .../apache/cassandra/io/util/FileUtils.java | 760 -- .../server/dao/AbstractDaoServiceTest.java | 2 +- .../server/dao/AbstractJpaDaoTest.java | 2 +- .../server/dao/AbstractNoSqlContainer.java | 96 + .../server/dao/AbstractRedisContainer.java | 51 + .../server/dao/CustomCassandraCQLUnit.java | 88 - .../server/dao/NoSqlDaoServiceTestSuite.java | 18 +- .../server/dao/PostgreSqlInitializer.java | 2 +- .../server/dao/RedisSqlTestSuite.java | 26 +- .../dao/TimescaleDaoServiceTestSuite.java | 2 +- .../server/dao/TimescaleSqlInitializer.java | 2 +- .../nosql/CassandraPartitionsCacheTest.java | 2 +- .../dao/service/AbstractServiceTest.java | 121 +- .../service/BaseAdminSettingsServiceTest.java | 63 +- .../service/BaseAlarmCommentServiceTest.java | 164 + .../dao/service/BaseAlarmServiceTest.java | 425 +- .../service/BaseApiUsageStateServiceTest.java | 25 +- .../service/BaseAssetProfileServiceTest.java | 49 +- .../dao/service/BaseAssetServiceTest.java | 85 +- .../dao/service/BaseCustomerServiceTest.java | 52 +- .../dao/service/BaseDashboardServiceTest.java | 118 +- .../BaseDeviceCredentialsCacheTest.java | 6 +- .../BaseDeviceCredentialsServiceTest.java | 65 +- .../service/BaseDeviceProfileServiceTest.java | 67 +- .../dao/service/BaseDeviceServiceTest.java | 101 +- .../dao/service/BaseEdgeEventServiceTest.java | 11 +- .../dao/service/BaseEdgeServiceTest.java | 88 +- .../BaseEntityServiceRegistryTest.java | 46 + .../dao/service/BaseEntityServiceTest.java | 403 +- .../BaseOAuth2ConfigTemplateServiceTest.java | 9 +- .../dao/service/BaseOAuth2ServiceTest.java | 15 +- .../service/BaseOtaPackageServiceTest.java | 159 +- .../dao/service/BaseQueueServiceTest.java | 122 +- .../dao/service/BaseRelationCacheTest.java | 2 +- .../dao/service/BaseRelationServiceTest.java | 26 +- .../dao/service/BaseRuleChainServiceTest.java | 65 +- .../service/BaseTenantProfileServiceTest.java | 52 +- .../dao/service/BaseTenantServiceTest.java | 112 +- .../dao/service/BaseUserServiceTest.java | 96 +- .../service/BaseWidgetTypeServiceTest.java | 78 +- .../service/BaseWidgetsBundleServiceTest.java | 71 +- .../server/dao/service/DaoNoSqlTest.java | 2 +- .../server/dao/service/DaoSqlTest.java | 2 +- .../server/dao/service/DaoTimescaleTest.java | 2 +- .../server/dao/service/DataValidatorTest.java | 43 +- .../dao/service/NoXssValidatorTest.java | 4 +- .../attributes/BaseAttributesServiceTest.java | 2 +- .../sql/AttributesServiceSqlTest.java | 2 +- .../service/event/BaseEventServiceTest.java | 8 +- .../event/sql/EventServiceSqlTest.java | 2 +- .../install/sql/EntitiesSchemaSqlTest.java | 2 +- .../sql/AdminSettingsServiceSqlTest.java | 2 +- .../sql/AlarmCommentServiceSqlTest.java | 23 + .../dao/service/sql/AlarmServiceSqlTest.java | 2 +- .../sql/ApiUsageStateServiceSqlTest.java | 2 +- .../sql/AssetProfileServiceSqlTest.java | 2 +- .../dao/service/sql/AssetServiceSqlTest.java | 2 +- .../service/sql/CustomerServiceSqlTest.java | 2 +- .../service/sql/DashboardServiceSqlTest.java | 2 +- .../DeviceCredentialsCacheServiceSqlTest.java | 2 +- .../sql/DeviceCredentialsServiceSqlTest.java | 2 +- .../sql/DeviceProfileServiceSqlTest.java | 2 +- .../dao/service/sql/DeviceServiceSqlTest.java | 2 +- .../service/sql/EdgeEventServiceSqlTest.java | 2 +- .../dao/service/sql/EdgeServiceSqlTest.java | 2 +- .../sql/EntityServiceRegistrySqlTest.java | 24 + .../dao/service/sql/EntityServiceSqlTest.java | 2 +- .../OAuth2ConfigTemplateServiceSqlTest.java | 2 +- .../dao/service/sql/OAuth2ServiceSqlTest.java | 2 +- .../service/sql/OtaPackageServiceSqlTest.java | 2 +- .../dao/service/sql/QueueServiceSqlTest.java | 2 +- .../dao/service/sql/RelationCacheSqlTest.java | 2 +- .../service/sql/RelationServiceSqlTest.java | 2 +- .../service/sql/RuleChainServiceSqlTest.java | 2 +- .../sql/TenantProfileServiceSqlTest.java | 2 +- .../dao/service/sql/TenantServiceSqlTest.java | 2 +- .../dao/service/sql/UserServiceSqlTest.java | 2 +- .../service/sql/WidgetTypeServiceSqlTest.java | 2 +- .../sql/WidgetsBundleServiceSqlTest.java | 2 +- .../timeseries/BaseTimeseriesServiceTest.java | 12 +- .../nosql/TimeseriesServiceNoSqlTest.java | 2 +- .../nosql/TimeseriesServiceTimescaleTest.java | 2 +- .../sql/TimeseriesServiceSqlTest.java | 2 +- .../dao/sql/alarm/JpaAlarmCommentDaoTest.java | 90 + .../server/dao/sql/alarm/JpaAlarmDaoTest.java | 246 +- .../server/dao/sql/asset/JpaAssetDaoTest.java | 2 +- .../dao/sql/audit/JpaAuditLogDaoTest.java | 2 +- .../JpaBaseComponentDescriptorDaoTest.java | 2 +- .../dao/sql/customer/JpaCustomerDaoTest.java | 2 +- .../dashboard/JpaDashboardInfoDaoTest.java | 2 +- .../device/JpaDeviceCredentialsDaoTest.java | 2 +- .../dao/sql/device/JpaDeviceDaoTest.java | 2 +- .../dao/sql/event/JpaBaseEventDaoTest.java | 2 +- .../DefaultEntityQueryRepositoryTest.java | 2 +- .../query/DefaultQueryLogComponentTest.java | 2 +- .../dao/sql/query/EntityDataAdapterTest.java | 8 +- .../dao/sql/query/EntityKeyMappingTest.java | 2 +- .../dao/sql/tenant/JpaTenantDaoTest.java | 2 +- .../sql/user/JpaUserCredentialsDaoTest.java | 2 +- .../server/dao/sql/user/JpaUserDaoTest.java | 2 +- .../dao/sql/user/JpaUserSettingsDaoTest.java | 104 + .../dao/sql/widget/JpaWidgetTypeDaoTest.java | 2 +- .../sql/widget/JpaWidgetsBundleDaoTest.java | 2 +- ...ctChunkedAggregationTimeseriesDaoTest.java | 2 +- ...esDaoPartitioningDaysAlwaysExistsTest.java | 2 +- ...sDaoPartitioningHoursAlwaysExistsTest.java | 2 +- ...artitioningIndefiniteAlwaysExistsTest.java | 2 +- ...aoPartitioningMinutesAlwaysExistsTest.java | 2 +- ...DaoPartitioningMonthsAlwaysExistsTest.java | 2 +- ...sDaoPartitioningYearsAlwaysExistsTest.java | 2 +- .../resources/application-test.properties | 11 +- .../test/resources/cassandra-test.properties | 2 +- dao/src/test/resources/logback.xml | 4 +- dao/src/test/resources/nosql-test.properties | 2 +- dao/src/test/resources/sql-test.properties | 2 +- docker/compose-utils.sh | 2 +- docker/docker-check-log-folders.sh | 2 +- docker/docker-compose.aws-sqs.yml | 2 +- docker/docker-compose.cassandra.volumes.yml | 2 +- docker/docker-compose.confluent.yml | 2 +- docker/docker-compose.hybrid.yml | 2 +- docker/docker-compose.kafka.yml | 2 +- docker/docker-compose.postgres.volumes.yml | 2 +- docker/docker-compose.postgres.yml | 2 +- docker/docker-compose.prometheus-grafana.yml | 2 +- docker/docker-compose.pubsub.yml | 2 +- docker/docker-compose.rabbitmq.yml | 2 +- .../docker-compose.redis-cluster.volumes.yml | 2 +- docker/docker-compose.redis-cluster.yml | 2 +- docker/docker-compose.redis.volumes.yml | 2 +- docker/docker-compose.redis.yml | 2 +- docker/docker-compose.service-bus.yml | 2 +- docker/docker-compose.volumes.yml | 2 +- docker/docker-compose.yml | 2 +- docker/docker-create-log-folders.sh | 2 +- docker/docker-install-tb.sh | 2 +- docker/docker-remove-services.sh | 2 +- docker/docker-start-services.sh | 2 +- docker/docker-stop-services.sh | 2 +- docker/docker-update-service.sh | 2 +- docker/docker-upgrade-tb.sh | 2 +- .../provisioning/dashboards/dashboard.yml | 2 +- .../provisioning/datasources/datasource.yml | 2 +- docker/monitoring/prometheus/prometheus.yml | 2 +- docker/tb-transports/coap/conf/logback.xml | 2 +- .../coap/conf/tb-coap-transport.conf | 2 +- docker/tb-transports/http/conf/logback.xml | 2 +- .../http/conf/tb-http-transport.conf | 2 +- docker/tb-transports/lwm2m/conf/logback.xml | 2 +- .../lwm2m/conf/tb-lwm2m-transport.conf | 2 +- docker/tb-transports/mqtt/conf/logback.xml | 2 +- .../mqtt/conf/tb-mqtt-transport.conf | 2 +- docker/tb-transports/snmp/conf/logback.xml | 2 +- .../snmp/conf/tb-snmp-transport.conf | 2 +- docker/tb-vc-executor/conf/logback.xml | 2 +- .../tb-vc-executor/conf/tb-vc-executor.conf | 2 +- license-header-template.txt | 2 +- msa/black-box-tests/README.md | 18 +- msa/black-box-tests/pom.xml | 7 +- .../server/msa/AbstractContainerTest.java | 2 +- .../server/msa/ContainerTestSuite.java | 3 +- .../server/msa/DisableUIListeners.java | 4 +- .../server/msa/DockerComposeExecutor.java | 2 +- .../msa/SeleniumRemoteWebDriverTest.java | 2 +- .../server/msa/TestCoapClient.java | 2 +- .../server/msa/TestCoapClientCallback.java | 2 +- .../thingsboard/server/msa/TestListener.java | 29 +- .../server/msa/TestProperties.java | 7 +- .../server/msa/TestRestClient.java | 65 +- .../server/msa/ThingsBoardDbInstaller.java | 5 +- .../org/thingsboard/server/msa/WsClient.java | 2 +- .../msa/connectivity/CoapClientTest.java | 2 +- .../msa/connectivity/HttpClientTest.java | 2 +- .../msa/connectivity/MqttClientTest.java | 2 +- .../connectivity/MqttGatewayClientTest.java | 2 +- .../server/msa/mapper/AttributesResponse.java | 2 +- .../msa/mapper/WsTelemetryResponse.java | 2 +- .../msa/prototypes/DevicePrototypes.java | 2 +- .../server/msa/ui/base/AbstractBasePage.java | 81 +- .../msa/ui/base/AbstractDriverBaseTest.java | 105 +- .../msa/ui/listeners/RetryAnalyzer.java | 2 +- .../msa/ui/listeners/RetryTestListener.java | 2 +- .../msa/ui/pages/CustomerPageElements.java | 22 +- .../msa/ui/pages/CustomerPageHelper.java | 31 +- .../msa/ui/pages/DashboardPageElements.java | 2 +- .../msa/ui/pages/DashboardPageHelper.java | 8 +- .../msa/ui/pages/LoginPageElements.java | 7 +- .../server/msa/ui/pages/LoginPageHelper.java | 5 +- .../ui/pages/OpenRuleChainPageElements.java | 9 +- .../msa/ui/pages/OpenRuleChainPageHelper.java | 7 +- .../msa/ui/pages/OtherPageElements.java | 54 +- .../msa/ui/pages/OtherPageElementsHelper.java | 64 +- .../msa/ui/pages/ProfilesPageElements.java | 177 + .../msa/ui/pages/ProfilesPageHelper.java | 181 + .../msa/ui/pages/RuleChainsPageElements.java | 33 +- .../msa/ui/pages/RuleChainsPageHelper.java | 16 +- .../msa/ui/pages/SideBarMenuViewElements.java | 17 +- .../msa/ui/pages/SideBarMenuViewHelper.java | 34 + .../AssetProfileEditMenuTest.java | 139 + .../CreateAssetProfileImportTest.java | 138 + .../CreateAssetProfileTest.java | 191 + .../DeleteAssetProfileTest.java | 141 + .../DeleteSeveralAssetProfilesTest.java | 117 + .../MakeAssetProfileDefaultTest.java | 85 + .../SearchAssetProfileTest.java | 67 + .../assetProfileSmoke/SortByNameTest.java | 137 + .../customerSmoke/CreateCustomerTest.java | 60 +- .../customerSmoke/CustomerEditMenuTest.java | 135 +- .../customerSmoke/DeleteCustomerTest.java | 41 +- .../DeleteSeveralCustomerTest.java | 45 +- .../ManageCustomersAssetsTest.java | 19 +- .../ManageCustomersDashboardsTest.java | 19 +- .../ManageCustomersDevicesTest.java | 19 +- .../ManageCustomersEdgesTest.java | 19 +- .../ManageCustomersUsersTest.java | 21 +- .../customerSmoke/SearchCustomerTest.java | 20 +- .../tests/customerSmoke/SortByNameTest.java | 28 +- .../CreateDeviceProfileImportTest.java | 138 + .../CreateDeviceProfileTest.java | 198 + .../DeleteDeviceProfileTest.java | 140 + .../DeleteSeveralDeviceProfilesTest.java | 108 + .../DeviceProfileEditMenuTest.java | 139 + .../MakeDeviceProfileDefaultTest.java | 75 + .../SearchDeviceProfileTest.java | 68 + .../deviceProfileSmoke/SortByNameTest.java | 137 + .../CreateRuleChainImportTest.java | 43 +- .../ruleChainsSmoke/CreateRuleChainTest.java | 55 +- .../ruleChainsSmoke/DeleteRuleChainTest.java | 66 +- .../DeleteSeveralRuleChainsTest.java | 54 +- .../MakeRuleChainRootTest.java | 27 +- .../ruleChainsSmoke/OpenRuleChainTest.java | 29 +- .../RuleChainEditMenuTest.java | 74 +- .../ruleChainsSmoke/SearchRuleChainTest.java | 20 +- .../tests/ruleChainsSmoke/SortByNameTest.java | 28 +- .../tests/ruleChainsSmoke/SortByTimeTest.java | 20 +- .../server/msa/ui/utils/Const.java | 17 +- .../msa/ui/utils/DataProviderCredential.java | 89 +- .../server/msa/ui/utils/EntityPrototypes.java | 72 +- .../src/test/resources/all.xml | 22 +- .../test/resources/assetProfileForImport.json | 10 + .../src/test/resources/config.properties | 0 .../src/test/resources/connectivity.xml | 2 +- .../resources/deviceProfileForImport.json | 29 + .../docker-compose.hybrid-test-extras.yml | 23 + .../docker-compose.postgres-test-extras.yml | 19 + .../docker-compose.rabbitmq-server.yml | 4 +- .../src/test/resources/docker-selenium.yml | 9 +- .../src/test/resources/forImport.txt | 2 +- .../src/test/resources/logback.xml | 2 +- ...forImport.json => ruleChainForImport.json} | 2 +- .../src/test/resources/smokesCustomer.xml | 2 +- .../src/test/resources/smokesProfiles.xml | 36 + .../src/test/resources/smokesRuleChain.xml | 2 +- .../src/test/resources/uiTests.xml | 22 +- msa/js-executor/api/httpServer.ts | 2 +- msa/js-executor/api/jsExecutor.models.ts | 2 +- msa/js-executor/api/jsExecutor.ts | 2 +- .../api/jsInvokeMessageProcessor.ts | 2 +- msa/js-executor/api/utils.ts | 2 +- .../config/custom-environment-variables.yml | 2 +- msa/js-executor/config/default.yml | 2 +- msa/js-executor/config/logger.ts | 2 +- msa/js-executor/config/tb-js-executor.conf | 2 +- msa/js-executor/docker/Dockerfile | 2 +- msa/js-executor/docker/start-js-executor.sh | 2 +- msa/js-executor/install.js | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 4 +- msa/js-executor/queue/awsSqsTemplate.ts | 2 +- msa/js-executor/queue/kafkaTemplate.ts | 2 +- msa/js-executor/queue/pubSubTemplate.ts | 2 +- msa/js-executor/queue/queue.models.ts | 2 +- msa/js-executor/queue/rabbitmqTemplate.ts | 2 +- msa/js-executor/queue/serviceBusTemplate.ts | 2 +- msa/js-executor/server.ts | 2 +- msa/pom.xml | 9 +- msa/tb-node/docker/Dockerfile | 2 +- msa/tb-node/docker/start-tb-node.sh | 2 +- msa/tb-node/pom.xml | 4 +- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-cassandra/start-db.sh | 2 +- msa/tb/docker-cassandra/stop-db.sh | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- msa/tb/docker-postgres/start-db.sh | 2 +- msa/tb/docker-postgres/stop-db.sh | 2 +- msa/tb/docker/install-tb.sh | 2 +- msa/tb/docker/logback.xml | 2 +- msa/tb/docker/start-tb.sh | 2 +- msa/tb/docker/thingsboard.conf | 2 +- msa/tb/docker/upgrade-tb.sh | 2 +- msa/tb/pom.xml | 6 +- msa/transport/coap/docker/Dockerfile | 2 +- .../coap/docker/start-tb-coap-transport.sh | 2 +- msa/transport/coap/pom.xml | 4 +- msa/transport/http/docker/Dockerfile | 2 +- .../http/docker/start-tb-http-transport.sh | 2 +- msa/transport/http/pom.xml | 4 +- msa/transport/lwm2m/docker/Dockerfile | 2 +- .../lwm2m/docker/start-tb-lwm2m-transport.sh | 2 +- msa/transport/lwm2m/pom.xml | 4 +- msa/transport/mqtt/docker/Dockerfile | 2 +- .../mqtt/docker/start-tb-mqtt-transport.sh | 2 +- msa/transport/mqtt/pom.xml | 4 +- msa/transport/pom.xml | 4 +- msa/transport/snmp/docker/Dockerfile | 2 +- .../snmp/docker/start-tb-snmp-transport.sh | 2 +- msa/transport/snmp/pom.xml | 4 +- msa/vc-executor-docker/docker/Dockerfile | 2 +- .../docker/start-tb-vc-executor.sh | 2 +- msa/vc-executor-docker/pom.xml | 4 +- msa/vc-executor/pom.xml | 4 +- msa/vc-executor/src/main/conf/logback.xml | 2 +- .../src/main/conf/tb-vc-executor.conf | 2 +- ...oardVersionControlExecutorApplication.java | 15 + ...VersionControlQueueRoutingInfoService.java | 2 +- ...ersionControlTenantRoutingInfoService.java | 2 +- .../src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-vc-executor.yml | 2 +- .../config/custom-environment-variables.yml | 2 +- msa/web-ui/config/default.yml | 2 +- msa/web-ui/config/logger.ts | 2 +- msa/web-ui/config/tb-web-ui.conf | 2 +- msa/web-ui/docker/Dockerfile | 2 +- msa/web-ui/docker/start-web-ui.sh | 2 +- msa/web-ui/install.js | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 4 +- msa/web-ui/server.ts | 2 +- netty-mqtt/pom.xml | 6 +- .../mqtt/ChannelClosedException.java | 2 +- .../thingsboard/mqtt/MqttChannelHandler.java | 2 +- .../java/org/thingsboard/mqtt/MqttClient.java | 2 +- .../thingsboard/mqtt/MqttClientCallback.java | 2 +- .../thingsboard/mqtt/MqttClientConfig.java | 2 +- .../org/thingsboard/mqtt/MqttClientImpl.java | 2 +- .../thingsboard/mqtt/MqttConnectResult.java | 2 +- .../org/thingsboard/mqtt/MqttHandler.java | 2 +- .../mqtt/MqttIncomingQos2Publish.java | 2 +- .../org/thingsboard/mqtt/MqttLastWill.java | 2 +- .../thingsboard/mqtt/MqttPendingPublish.java | 2 +- .../mqtt/MqttPendingSubscription.java | 2 +- .../mqtt/MqttPendingUnsubscription.java | 2 +- .../org/thingsboard/mqtt/MqttPingHandler.java | 2 +- .../thingsboard/mqtt/MqttSubscription.java | 2 +- .../thingsboard/mqtt/PendingOperation.java | 2 +- .../mqtt/RetransmissionHandler.java | 2 +- .../thingsboard/mqtt/MqttPingHandlerTest.java | 2 +- .../integration/IntegrationTestSuite.java | 2 +- .../mqtt/integration/MqttIntegrationTest.java | 2 +- .../mqtt/integration/server/MqttServer.java | 2 +- .../server/MqttTransportHandler.java | 2 +- packaging/java/assembly/windows.xml | 2 +- packaging/java/build.gradle | 2 +- packaging/java/scripts/install/install.sh | 2 +- .../java/scripts/install/install_dev_db.sh | 2 +- packaging/java/scripts/install/logback.xml | 2 +- packaging/java/scripts/install/upgrade.sh | 2 +- .../java/scripts/install/upgrade_dev_db.sh | 2 +- packaging/js/assembly/windows.xml | 2 +- packaging/js/build.gradle | 2 +- pom.xml | 70 +- pull_request_template.md | 2 +- rest-client/pom.xml | 8 +- .../thingsboard/rest/client/RestClient.java | 167 +- .../rest/client/utils/RestJsonConverter.java | 2 +- rest-client/src/main/resources/logback.xml | 2 +- rule-engine/pom.xml | 4 +- rule-engine/rule-engine-api/pom.xml | 4 +- .../engine/api/EmptyNodeConfiguration.java | 2 +- .../rule/engine/api/MailService.java | 7 +- .../rule/engine/api/NodeConfiguration.java | 2 +- .../rule/engine/api/NodeDefinition.java | 2 +- .../rule/engine/api/NotificationCenter.java | 45 + .../engine/api/RuleEngineAlarmService.java | 51 +- .../api/RuleEngineApiUsageStateService.java | 2 +- .../api/RuleEngineAssetProfileCache.java | 2 +- .../api/RuleEngineDeviceProfileCache.java | 2 +- .../api/RuleEngineDeviceRpcRequest.java | 2 +- .../api/RuleEngineDeviceRpcResponse.java | 2 +- .../rule/engine/api/RuleEngineRpcService.java | 2 +- .../api/RuleEngineTelemetryService.java | 2 +- .../thingsboard/rule/engine/api/RuleNode.java | 2 +- .../rule/engine/api/ScriptEngine.java | 2 +- .../rule/engine/api/SmsService.java | 2 +- .../rule/engine/api/TbContext.java | 27 +- .../thingsboard/rule/engine/api/TbEmail.java | 2 +- .../thingsboard/rule/engine/api/TbNode.java | 2 +- .../rule/engine/api/TbNodeConfiguration.java | 2 +- .../rule/engine/api/TbNodeException.java | 2 +- .../rule/engine/api/TbNodeState.java | 2 +- .../rule/engine/api/TbRelationTypes.java | 2 +- .../rule/engine/api/msg/DeviceAttributes.java | 2 +- .../DeviceAttributesEventNotificationMsg.java | 2 +- ...eviceCredentialsUpdateNotificationMsg.java | 2 +- .../engine/api/msg/DeviceEdgeUpdateMsg.java | 2 +- .../rule/engine/api/msg/DeviceMetaData.java | 2 +- .../api/msg/DeviceNameOrTypeUpdateMsg.java | 2 +- .../rule/engine/api/slack/SlackService.java | 34 + .../rule/engine/api/sms/SmsSender.java | 2 +- .../rule/engine/api/sms/SmsSenderFactory.java | 2 +- .../api/sms/exception/SmsException.java | 2 +- .../api/sms/exception/SmsParseException.java | 2 +- .../api/sms/exception/SmsSendException.java | 2 +- .../rule/engine/api/util/TbNodeUtils.java | 2 +- .../rule/engine/api/util/TbNodeUtilsTest.java | 2 +- rule-engine/rule-engine-components/pom.xml | 20 +- .../engine/action/TbAbstractAlarmNode.java | 2 +- .../TbAbstractAlarmNodeConfiguration.java | 2 +- .../action/TbAbstractCustomerActionNode.java | 2 +- ...stractCustomerActionNodeConfiguration.java | 2 +- .../action/TbAbstractRelationActionNode.java | 2 +- ...stractRelationActionNodeConfiguration.java | 2 +- .../rule/engine/action/TbAlarmResult.java | 7 +- .../engine/action/TbAssignToCustomerNode.java | 2 +- .../TbAssignToCustomerNodeConfiguration.java | 2 +- .../rule/engine/action/TbClearAlarmNode.java | 41 +- .../action/TbClearAlarmNodeConfiguration.java | 2 +- .../TbCopyAttributesToEntityViewNode.java | 4 +- .../rule/engine/action/TbCreateAlarmNode.java | 52 +- .../TbCreateAlarmNodeConfiguration.java | 2 +- .../engine/action/TbCreateRelationNode.java | 2 +- .../TbCreateRelationNodeConfiguration.java | 2 +- .../engine/action/TbDeleteRelationNode.java | 2 +- .../TbDeleteRelationNodeConfiguration.java | 2 +- .../rule/engine/action/TbLogNode.java | 24 +- .../engine/action/TbLogNodeConfiguration.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../action/TbMsgCountNodeConfiguration.java | 2 +- .../TbSaveToCustomCassandraTableNode.java | 2 +- ...CustomCassandraTableNodeConfiguration.java | 2 +- .../action/TbUnassignFromCustomerNode.java | 2 +- ...UnassignFromCustomerNodeConfiguration.java | 2 +- .../rule/engine/aws/sns/TbSnsNode.java | 4 +- .../aws/sns/TbSnsNodeConfiguration.java | 2 +- .../rule/engine/aws/sqs/TbSqsNode.java | 4 +- .../aws/sqs/TbSqsNodeConfiguration.java | 2 +- .../credentials/AnonymousCredentials.java | 2 +- .../engine/credentials/BasicCredentials.java | 2 +- .../credentials/CertPemCredentials.java | 43 +- .../engine/credentials/ClientCredentials.java | 2 +- .../engine/credentials/CredentialsType.java | 2 +- .../engine/data/DeviceRelationsQuery.java | 2 +- .../rule/engine/data/RelationsQuery.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 8 +- .../TbMsgGeneratorNodeConfiguration.java | 3 +- .../deduplication/DeduplicationData.java | 45 + .../engine/deduplication/DeduplicationId.java | 22 + .../deduplication/DeduplicationStrategy.java | 22 + .../deduplication/TbMsgDeduplicationNode.java | 223 + .../TbMsgDeduplicationNodeConfiguration.java | 44 + .../rule/engine/delay/TbMsgDelayNode.java | 3 +- .../delay/TbMsgDelayNodeConfiguration.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 19 +- .../edge/BaseTbMsgPushNodeConfiguration.java | 2 +- .../engine/edge/TbMsgPushToCloudNode.java | 2 +- .../TbMsgPushToCloudNodeConfiguration.java | 2 +- .../rule/engine/edge/TbMsgPushToEdgeNode.java | 2 +- .../TbMsgPushToEdgeNodeConfiguration.java | 2 +- .../filter/TbAbstractTypeSwitchNode.java | 45 + .../engine/filter/TbAssetTypeSwitchNode.java | 54 + .../engine/filter/TbCheckAlarmStatusNode.java | 4 +- .../filter/TbCheckAlarmStatusNodeConfig.java | 2 +- .../engine/filter/TbCheckMessageNode.java | 11 +- .../TbCheckMessageNodeConfiguration.java | 2 +- .../engine/filter/TbCheckRelationNode.java | 9 +- .../TbCheckRelationNodeConfiguration.java | 2 +- .../engine/filter/TbDeviceTypeSwitchNode.java | 54 + .../rule/engine/filter/TbJsFilterNode.java | 9 +- .../filter/TbJsFilterNodeConfiguration.java | 2 +- .../rule/engine/filter/TbJsSwitchNode.java | 6 +- .../filter/TbJsSwitchNodeConfiguration.java | 2 +- .../engine/filter/TbMsgTypeFilterNode.java | 2 +- .../TbMsgTypeFilterNodeConfiguration.java | 2 +- .../engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../filter/TbOriginatorTypeFilterNode.java | 8 +- ...OriginatorTypeFilterNodeConfiguration.java | 2 +- .../filter/TbOriginatorTypeSwitchNode.java | 26 +- .../rule/engine/flow/TbAckNode.java | 2 +- .../rule/engine/flow/TbCheckpointNode.java | 2 +- .../flow/TbCheckpointNodeConfiguration.java | 2 +- .../engine/flow/TbRuleChainInputNode.java | 3 +- .../TbRuleChainInputNodeConfiguration.java | 2 +- .../engine/flow/TbRuleChainOutputNode.java | 2 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 4 +- .../gcp/pubsub/TbPubSubNodeConfiguration.java | 2 +- .../engine/geo/AbstractGeofencingNode.java | 2 +- .../rule/engine/geo/Coordinates.java | 2 +- .../engine/geo/EntityGeofencingState.java | 2 +- .../thingsboard/rule/engine/geo/GeoUtil.java | 2 +- .../rule/engine/geo/Perimeter.java | 2 +- .../rule/engine/geo/PerimeterType.java | 2 +- .../rule/engine/geo/RangeUnit.java | 2 +- .../engine/geo/TbGpsGeofencingActionNode.java | 2 +- ...bGpsGeofencingActionNodeConfiguration.java | 2 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 29 +- ...bGpsGeofencingFilterNodeConfiguration.java | 2 +- .../rule/engine/kafka/TbKafkaNode.java | 4 +- .../kafka/TbKafkaNodeConfiguration.java | 2 +- .../rule/engine/mail/TbMsgToEmailNode.java | 2 +- .../mail/TbMsgToEmailNodeConfiguration.java | 2 +- .../rule/engine/mail/TbSendEmailNode.java | 4 +- .../mail/TbSendEmailNodeConfiguration.java | 2 +- .../rule/engine/math/TbMathArgument.java | 2 +- .../rule/engine/math/TbMathArgumentType.java | 2 +- .../rule/engine/math/TbMathArgumentValue.java | 2 +- .../rule/engine/math/TbMathNode.java | 2 +- .../engine/math/TbMathNodeConfiguration.java | 2 +- .../rule/engine/math/TbMathResult.java | 2 +- .../math/TbRuleNodeMathFunctionType.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 2 +- .../CalculateDeltaNodeConfiguration.java | 2 +- .../metadata/TbAbstractGetAttributesNode.java | 14 +- .../TbAbstractGetEntityDetailsNode.java | 2 +- ...ractGetEntityDetailsNodeConfiguration.java | 2 +- .../engine/metadata/TbEntityGetAttrNode.java | 2 +- .../TbFetchDeviceCredentialsNode.java | 8 +- ...tchDeviceCredentialsNodeConfiguration.java | 2 +- .../engine/metadata/TbGetAttributesNode.java | 4 +- .../TbGetAttributesNodeConfiguration.java | 2 +- .../metadata/TbGetCustomerAttributeNode.java | 10 +- .../metadata/TbGetCustomerDetailsNode.java | 4 +- ...TbGetCustomerDetailsNodeConfiguration.java | 2 +- .../engine/metadata/TbGetDeviceAttrNode.java | 2 +- .../TbGetDeviceAttrNodeConfiguration.java | 2 +- .../TbGetEntityAttrNodeConfiguration.java | 2 +- .../TbGetOriginatorFieldsConfiguration.java | 2 +- .../metadata/TbGetOriginatorFieldsNode.java | 2 +- .../TbGetRelatedAttrNodeConfiguration.java | 2 +- .../metadata/TbGetRelatedAttributeNode.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 2 +- .../TbGetTelemetryNodeConfiguration.java | 2 +- .../metadata/TbGetTenantAttributeNode.java | 2 +- .../metadata/TbGetTenantDetailsNode.java | 2 +- .../TbGetTenantDetailsNodeConfiguration.java | 2 +- .../rule/engine/mqtt/TbMqttNode.java | 4 +- .../engine/mqtt/TbMqttNodeConfiguration.java | 2 +- .../mqtt/azure/AzureIotHubSasCredentials.java | 2 +- .../engine/mqtt/azure/TbAzureIotHubNode.java | 4 +- .../azure/TbAzureIotHubNodeConfiguration.java | 2 +- .../notification/TbNotificationNode.java | 84 + .../TbNotificationNodeConfiguration.java | 40 + .../rule/engine/notification/TbSlackNode.java | 69 + .../TbSlackNodeConfiguration.java | 50 + .../rule/engine/profile/AlarmEvalResult.java | 2 +- .../rule/engine/profile/AlarmRuleState.java | 2 +- .../rule/engine/profile/AlarmState.java | 51 +- .../profile/AlarmStateUpdateResult.java | 2 +- .../rule/engine/profile/DataSnapshot.java | 2 +- .../rule/engine/profile/DeviceState.java | 2 +- .../profile/DynamicPredicateValueCtx.java | 2 +- .../profile/DynamicPredicateValueCtxImpl.java | 2 +- .../rule/engine/profile/EntityKeyValue.java | 2 +- .../engine/profile/NumericParseException.java | 2 +- .../rule/engine/profile/ProfileState.java | 2 +- .../rule/engine/profile/SnapshotUpdate.java | 2 +- .../engine/profile/TbDeviceProfileNode.java | 2 +- .../TbDeviceProfileNodeConfiguration.java | 2 +- .../state/PersistedAlarmRuleState.java | 2 +- .../profile/state/PersistedAlarmState.java | 2 +- .../profile/state/PersistedDeviceState.java | 2 +- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 4 +- .../rabbitmq/TbRabbitMqNodeConfiguration.java | 2 +- .../rule/engine/rest/TbHttpClient.java | 18 +- .../rule/engine/rest/TbRestApiCallNode.java | 4 +- .../rest/TbRestApiCallNodeConfiguration.java | 7 +- .../rule/engine/rpc/TbSendRPCReplyNode.java | 2 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../rpc/TbSendRpcReplyNodeConfiguration.java | 2 +- .../TbSendRpcRequestNodeConfiguration.java | 2 +- .../rule/engine/sms/TbSendSmsNode.java | 4 +- .../sms/TbSendSmsNodeConfiguration.java | 2 +- .../AttributesDeleteNodeCallback.java | 2 +- .../AttributesUpdateNodeCallback.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../TbMsgAttributesNodeConfiguration.java | 2 +- .../telemetry/TbMsgDeleteAttributesNode.java | 2 +- ...bMsgDeleteAttributesNodeConfiguration.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- .../TbMsgTimeseriesNodeConfiguration.java | 2 +- .../telemetry/TelemetryNodeCallback.java | 2 +- .../TbSynchronizationBeginNode.java | 2 +- .../transaction/TbSynchronizationEndNode.java | 2 +- .../MultipleTbMsgsCallbackWrapper.java | 2 +- .../transform/TbAbstractTransformNode.java | 2 +- .../transform/TbChangeOriginatorNode.java | 2 +- .../TbChangeOriginatorNodeConfiguration.java | 2 +- .../rule/engine/transform/TbCopyKeysNode.java | 2 +- .../TbCopyKeysNodeConfiguration.java | 2 +- .../engine/transform/TbDeleteKeysNode.java | 2 +- .../TbDeleteKeysNodeConfiguration.java | 2 +- .../rule/engine/transform/TbJsonPathNode.java | 2 +- .../TbJsonPathNodeConfiguration.java | 2 +- .../transform/TbMsgCallbackWrapper.java | 2 +- .../engine/transform/TbRenameKeysNode.java | 2 +- .../TbRenameKeysNodeConfiguration.java | 2 +- .../engine/transform/TbSplitArrayMsgNode.java | 4 +- .../engine/transform/TbTransformMsgNode.java | 2 +- .../TbTransformMsgNodeConfiguration.java | 2 +- .../TbTransformNodeConfiguration.java | 2 +- .../EntitiesAlarmOriginatorIdAsyncLoader.java | 2 +- .../util/EntitiesByNameAndTypeLoader.java | 2 +- .../util/EntitiesCustomerIdAsyncLoader.java | 2 +- .../util/EntitiesFieldsAsyncLoader.java | 2 +- .../EntitiesRelatedDeviceIdAsyncLoader.java | 2 +- .../EntitiesRelatedEntityIdAsyncLoader.java | 2 +- .../rule/engine/util/EntityContainer.java | 2 +- .../rule/engine/util/EntityDetails.java | 2 +- .../rule/engine/util/TenantIdLoader.java | 20 +- .../static/rulenode/rulenode-core-config.js | 2 +- .../rule/engine/action/TbAlarmNodeTest.java | 307 +- .../action/TbCreateRelationNodeTest.java | 2 +- .../rule/engine/action/TbLogNodeTest.java | 64 +- .../action/TbMsgDeduplicationNodeTest.java | 402 + .../credentials/CertPemCredentialsTest.java | 73 + .../engine/edge/TbMsgPushToEdgeNodeTest.java | 2 +- .../filter/TbAssetTypeSwitchNodeTest.java | 126 + .../filter/TbDeviceTypeSwitchNodeTest.java | 126 + .../engine/filter/TbJsFilterNodeTest.java | 2 +- .../engine/filter/TbJsSwitchNodeTest.java | 2 +- .../rule/engine/geo/TbGeoUtilTest.java | 2 +- .../engine/mail/TbMsgToEmailNodeTest.java | 2 +- .../engine/math/TbMathArgumentValueTest.java | 2 +- .../rule/engine/math/TbMathNodeTest.java | 2 +- .../metadata/AbstractAttributeNodeTest.java | 2 +- .../TbAbstractGetAttributesNodeTest.java | 115 +- .../TbFetchDeviceCredentialsNodeTest.java | 2 +- .../TbGetCustomerAttributeNodeTest.java | 2 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../metadata/TbGetTelemetryNodeTest.java | 15 +- .../TbGetTenantAttributeNodeTest.java | 2 +- .../rule/engine/profile/AlarmStateTest.java | 2 +- .../rule/engine/profile/DeviceStateTest.java | 19 +- .../profile/TbDeviceProfileNodeTest.java | 124 +- .../rule/engine/rest/TbHttpClientTest.java | 2 +- .../engine/rest/TbRestApiCallNodeTest.java | 2 +- .../engine/rpc/TbSendRPCReplyNodeTest.java | 2 +- .../TbMsgDeleteAttributesNodeTest.java | 2 +- .../transform/TbChangeOriginatorNodeTest.java | 2 +- .../engine/transform/TbCopyKeysNodeTest.java | 2 +- .../transform/TbDeleteKeysNodeTest.java | 2 +- .../engine/transform/TbJsonPathNodeTest.java | 2 +- .../transform/TbMsgDeduplicationNodeTest.java | 401 + .../transform/TbRenameKeysNodeTest.java | 2 +- .../transform/TbSplitArrayMsgNodeTest.java | 2 +- .../transform/TbTransformMsgNodeTest.java | 2 +- .../rule/engine/util/TenantIdLoaderTest.java | 52 +- .../src/test/resources/pem/empty.pem | 0 .../src/test/resources/pem/tb-cloud-chain.pem | 103 + .../src/test/resources/pem/tb-cloud.pem | 32 + tools/pom.xml | 4 +- .../client/tools/MqttSslClient.java | 2 +- .../tools/migrator/DictionaryParser.java | 2 +- .../client/tools/migrator/MigratorTool.java | 2 +- .../client/tools/migrator/PgCaMigrator.java | 2 +- .../tools/migrator/RelatedEntitiesParser.java | 2 +- .../client/tools/migrator/WriterBuilder.java | 2 +- tools/src/main/python/mqtt-send-telemetry.py | 2 +- .../main/python/one-way-ssl-mqtt-client.py | 2 +- tools/src/main/python/simple-mqtt-client.py | 2 +- .../main/python/two-way-ssl-mqtt-client.py | 2 +- tools/src/main/shell/client.keygen.sh | 2 +- .../lwM2M_cfssl_chain_clients_for_test.sh | 2 +- .../lwm2m/lwm2m_cfssl_chain_all_for_test.sh | 2 +- .../lwm2m_cfssl_chain_server_for_test.sh | 2 +- tools/src/main/shell/server.keygen.sh | 2 +- transport/coap/pom.xml | 4 +- transport/coap/src/main/conf/logback.xml | 2 +- .../coap/src/main/conf/tb-coap-transport.conf | 2 +- .../ThingsboardCoapTransportApplication.java | 2 +- transport/coap/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-coap-transport.yml | 2 +- transport/http/pom.xml | 4 +- transport/http/src/main/conf/logback.xml | 2 +- .../http/src/main/conf/tb-http-transport.conf | 2 +- .../ThingsboardHttpTransportApplication.java | 2 +- transport/http/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-http-transport.yml | 2 +- transport/lwm2m/pom.xml | 4 +- transport/lwm2m/src/main/conf/logback.xml | 2 +- .../src/main/conf/tb-lwm2m-transport.conf | 2 +- .../ThingsboardLwm2mTransportApplication.java | 2 +- .../lwm2m/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-lwm2m-transport.yml | 2 +- transport/mqtt/pom.xml | 4 +- transport/mqtt/src/main/conf/logback.xml | 2 +- .../mqtt/src/main/conf/tb-mqtt-transport.conf | 2 +- .../ThingsboardMqttTransportApplication.java | 2 +- transport/mqtt/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 2 +- transport/pom.xml | 4 +- transport/snmp/pom.xml | 4 +- transport/snmp/src/main/conf/logback.xml | 2 +- .../snmp/src/main/conf/tb-snmp-transport.conf | 2 +- .../ThingsboardSnmpTransportApplication.java | 2 +- transport/snmp/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-snmp-transport.yml | 2 +- ui-ngx/.browserslistrc | 16 - ui-ngx/.editorconfig | 2 +- ui-ngx/.eslintrc.json | 67 + ui-ngx/.gitignore | 1 + ui-ngx/angular.json | 47 +- ui-ngx/e2e/protractor.conf.js | 2 +- ui-ngx/e2e/src/app.e2e-spec.ts | 2 +- ui-ngx/e2e/src/app.po.ts | 2 +- ui-ngx/extra-webpack.config.js | 19 +- ui-ngx/generate-types.js | 3 +- ui-ngx/package.json | 170 +- ui-ngx/pom.xml | 4 +- ui-ngx/proxy.conf.js | 2 +- ui-ngx/src/app/app-routing.module.ts | 2 +- ui-ngx/src/app/app.component.html | 2 +- ui-ngx/src/app/app.component.scss | 2 +- ui-ngx/src/app/app.component.ts | 11 +- ui-ngx/src/app/app.module.ts | 2 +- .../app/core/api/alarm-data-subscription.ts | 6 +- ui-ngx/src/app/core/api/alarm-data.service.ts | 2 +- ui-ngx/src/app/core/api/alias-controller.ts | 2 +- ui-ngx/src/app/core/api/data-aggregator.ts | 2 +- .../app/core/api/entity-data-subscription.ts | 6 +- .../src/app/core/api/entity-data.service.ts | 2 +- ui-ngx/src/app/core/api/public-api.ts | 2 +- ui-ngx/src/app/core/api/widget-api.models.ts | 7 +- .../src/app/core/api/widget-subscription.ts | 149 +- ui-ngx/src/app/core/auth/auth.actions.ts | 14 +- ui-ngx/src/app/core/auth/auth.effects.ts | 42 + ui-ngx/src/app/core/auth/auth.models.ts | 4 +- ui-ngx/src/app/core/auth/auth.reducer.ts | 24 +- ui-ngx/src/app/core/auth/auth.selectors.ts | 40 +- ui-ngx/src/app/core/auth/auth.service.spec.ts | 2 +- ui-ngx/src/app/core/auth/auth.service.ts | 20 +- ui-ngx/src/app/core/auth/public-api.ts | 3 +- ui-ngx/src/app/core/core.module.ts | 2 +- ui-ngx/src/app/core/core.state.ts | 4 +- ui-ngx/src/app/core/css/css.js | 2 +- ui-ngx/src/app/core/guards/auth.guard.ts | 2 +- .../app/core/guards/confirm-on-exit.guard.ts | 6 +- ui-ngx/src/app/core/http/admin.service.ts | 2 +- .../app/core/http/alarm-comment.service.ts | 46 + ui-ngx/src/app/core/http/alarm.service.ts | 16 +- .../app/core/http/asset-profile.service.ts | 2 +- ui-ngx/src/app/core/http/asset.service.ts | 2 +- ui-ngx/src/app/core/http/attribute.service.ts | 2 +- ui-ngx/src/app/core/http/audit-log.service.ts | 2 +- .../core/http/component-descriptor.service.ts | 2 +- ui-ngx/src/app/core/http/customer.service.ts | 2 +- ui-ngx/src/app/core/http/dashboard.service.ts | 2 +- .../app/core/http/device-profile.service.ts | 2 +- ui-ngx/src/app/core/http/device.service.ts | 2 +- ui-ngx/src/app/core/http/edge.service.ts | 8 +- .../http/entities-version-control.service.ts | 2 +- .../app/core/http/entity-relation.service.ts | 2 +- .../src/app/core/http/entity-view.service.ts | 2 +- ui-ngx/src/app/core/http/entity.service.ts | 26 +- ui-ngx/src/app/core/http/event.service.ts | 2 +- ui-ngx/src/app/core/http/http-utils.ts | 2 +- .../src/app/core/http/notification.service.ts | 172 + ui-ngx/src/app/core/http/oauth2.service.ts | 2 +- .../src/app/core/http/ota-package.service.ts | 2 +- ui-ngx/src/app/core/http/public-api.ts | 4 +- ui-ngx/src/app/core/http/queue.service.ts | 2 +- ui-ngx/src/app/core/http/resource.service.ts | 2 +- .../src/app/core/http/rule-chain.service.ts | 2 +- .../app/core/http/tenant-profile.service.ts | 11 +- ui-ngx/src/app/core/http/tenant.service.ts | 2 +- .../http/two-factor-authentication.service.ts | 2 +- .../src/app/core/http/ui-settings.service.ts | 2 +- .../app/core/http/user-settings.service.ts | 54 + ui-ngx/src/app/core/http/user.service.ts | 14 +- ui-ngx/src/app/core/http/widget.service.ts | 18 +- .../interceptors/global-http-interceptor.ts | 2 +- .../core/interceptors/interceptor-config.ts | 2 +- .../interceptors/interceptor-http-params.ts | 2 +- .../src/app/core/interceptors/load.actions.ts | 2 +- .../src/app/core/interceptors/load.models.ts | 2 +- .../src/app/core/interceptors/load.reducer.ts | 2 +- .../app/core/interceptors/load.selectors.ts | 4 +- .../local-storage/local-storage.service.ts | 2 +- .../app/core/meta-reducers/debug.reducer.ts | 2 +- .../init-state-from-local-storage.reducer.ts | 2 +- .../core/notification/notification.actions.ts | 2 +- .../core/notification/notification.effects.ts | 20 +- .../core/notification/notification.models.ts | 2 +- .../core/notification/notification.reducer.ts | 2 +- ui-ngx/src/app/core/operator/enterZone.ts | 2 +- ui-ngx/src/app/core/public-api.ts | 2 +- .../core/services/active-component.service.ts | 41 + .../src/app/core/services/broadcast.models.ts | 2 +- .../app/core/services/broadcast.service.ts | 2 +- .../core/services/dashboard-utils.service.ts | 2 +- .../src/app/core/services/dialog.service.ts | 2 +- .../dynamic-component-factory.service.ts | 14 +- ui-ngx/src/app/core/services/help.service.ts | 2 +- .../app/core/services/item-buffer.service.ts | 2 +- ui-ngx/src/app/core/services/menu.models.ts | 6 +- ui-ngx/src/app/core/services/menu.service.ts | 438 +- .../src/app/core/services/mobile.service.ts | 2 +- ui-ngx/src/app/core/services/public-api.ts | 4 +- ui-ngx/src/app/core/services/raf.service.ts | 6 +- .../app/core/services/resources.service.ts | 6 +- .../script/node-script-test.service.ts | 2 +- ui-ngx/src/app/core/services/time.service.ts | 2 +- ui-ngx/src/app/core/services/title.service.ts | 2 +- ...rvice.ts => toast-notification.service.ts} | 4 +- ui-ngx/src/app/core/services/utils.service.ts | 38 +- .../src/app/core/services/window.service.ts | 2 +- .../src/app/core/settings/settings.actions.ts | 2 +- .../src/app/core/settings/settings.effects.ts | 28 +- .../src/app/core/settings/settings.models.ts | 2 +- .../src/app/core/settings/settings.reducer.ts | 2 +- .../app/core/settings/settings.selectors.ts | 4 +- .../src/app/core/settings/settings.utils.ts | 2 +- .../translate/missing-translate-handler.ts | 2 +- .../translate/translate-default-compiler.ts | 13 +- .../translate/translate-default-parser.ts | 6 +- ui-ngx/src/app/core/utils.ts | 42 +- .../core/ws/notification-websocket.service.ts | 131 + .../core/ws/telemetry-websocket.service.ts | 290 +- ui-ngx/src/app/core/ws/websocket.service.ts | 261 + .../app/modules/common/modules-map.models.ts | 2 +- ui-ngx/src/app/modules/common/modules-map.ts | 14 +- .../dashboard/dashboard-pages.module.ts | 2 +- .../dashboard-pages.routing.module.ts | 10 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../alarm/alarm-assignee-panel.component.html | 51 + .../alarm/alarm-assignee-panel.component.scss | 87 + .../alarm/alarm-assignee-panel.component.ts | 235 + .../alarm/alarm-assignee.component.html | 30 + .../alarm/alarm-assignee.component.scss | 42 + .../alarm/alarm-assignee.component.ts | 157 + .../alarm/alarm-comment-dialog.component.html | 33 + .../alarm/alarm-comment-dialog.component.ts | 50 + .../alarm/alarm-comment.component.html | 181 + .../alarm/alarm-comment.component.scss | 220 + .../alarm/alarm-comment.component.ts | 325 + .../alarm/alarm-details-dialog.component.html | 74 +- .../alarm/alarm-details-dialog.component.scss | 62 + .../alarm/alarm-details-dialog.component.ts | 68 +- .../components/alarm/alarm-table-config.ts | 172 +- .../alarm/alarm-table-header.component.html | 2 +- .../alarm/alarm-table-header.component.scss | 25 +- .../alarm/alarm-table-header.component.ts | 2 +- .../alarm/alarm-table.component.html | 4 +- .../alarm/alarm-table.component.scss | 41 +- .../components/alarm/alarm-table.component.ts | 34 +- ...aliases-entity-autocomplete.component.html | 7 +- .../aliases-entity-autocomplete.component.ts | 8 +- ...aliases-entity-select-panel.component.html | 2 +- ...aliases-entity-select-panel.component.scss | 2 +- .../aliases-entity-select-panel.component.ts | 2 +- .../aliases-entity-select.component.html | 2 +- .../aliases-entity-select.component.scss | 2 +- .../alias/aliases-entity-select.component.ts | 2 +- .../alias/entity-alias-dialog.component.html | 4 +- .../alias/entity-alias-dialog.component.scss | 2 +- .../alias/entity-alias-dialog.component.ts | 16 +- .../alias/entity-alias-select.component.html | 9 +- .../entity-alias-select.component.models.ts | 2 +- .../alias/entity-alias-select.component.scss | 8 +- .../alias/entity-alias-select.component.ts | 14 +- .../entity-aliases-dialog.component.html | 9 +- .../entity-aliases-dialog.component.scss | 4 +- .../alias/entity-aliases-dialog.component.ts | 26 +- .../add-attribute-dialog.component.html | 4 +- .../add-attribute-dialog.component.ts | 10 +- ...-widget-to-dashboard-dialog.component.html | 4 +- ...-widget-to-dashboard-dialog.component.scss | 15 +- ...dd-widget-to-dashboard-dialog.component.ts | 10 +- .../attribute/attribute-table.component.html | 22 +- .../attribute/attribute-table.component.scss | 32 +- .../attribute/attribute-table.component.ts | 2 +- .../edit-attribute-value-panel.component.html | 4 +- .../edit-attribute-value-panel.component.scss | 2 +- .../edit-attribute-value-panel.component.ts | 10 +- .../audit-log-details-dialog.component.html | 4 +- .../audit-log-details-dialog.component.scss | 2 +- .../audit-log-details-dialog.component.ts | 2 +- .../audit-log/audit-log-table-config.ts | 2 +- .../audit-log/audit-log-table.component.html | 2 +- .../audit-log/audit-log-table.component.scss | 2 +- .../audit-log/audit-log-table.component.ts | 2 +- .../add-widget-dialog.component.html | 4 +- .../add-widget-dialog.component.ts | 10 +- .../dashboard-image-dialog.component.html | 4 +- .../dashboard-image-dialog.component.scss | 4 +- .../dashboard-image-dialog.component.ts | 10 +- .../dashboard-page.component.html | 6 +- .../dashboard-page.component.scss | 14 +- .../dashboard-page.component.ts | 8 +- .../dashboard-page/dashboard-page.models.ts | 2 +- .../dashboard-settings-dialog.component.html | 4 +- .../dashboard-settings-dialog.component.scss | 5 +- .../dashboard-settings-dialog.component.ts | 12 +- .../dashboard-state.component.html | 2 +- .../dashboard-state.component.scss | 2 +- .../dashboard-state.component.ts | 2 +- .../dashboard-toolbar.component.html | 2 +- .../dashboard-toolbar.component.scss | 42 +- .../dashboard-toolbar.component.ts | 2 +- .../dashboard-widget-select.component.html | 14 +- .../dashboard-widget-select.component.scss | 23 +- .../dashboard-widget-select.component.ts | 2 +- .../dashboard-page/edit-widget.component.html | 2 +- .../dashboard-page/edit-widget.component.ts | 10 +- .../layout/dashboard-layout.component.html | 4 +- .../layout/dashboard-layout.component.scss | 14 +- .../layout/dashboard-layout.component.ts | 2 +- .../dashboard-page/layout/layout.models.ts | 2 +- ...ge-dashboard-layouts-dialog.component.html | 14 +- ...ge-dashboard-layouts-dialog.component.scss | 32 +- ...nage-dashboard-layouts-dialog.component.ts | 14 +- .../dashboard-state-dialog.component.html | 4 +- .../dashboard-state-dialog.component.ts | 16 +- .../default-state-controller.component.html | 2 +- .../default-state-controller.component.scss | 14 +- .../default-state-controller.component.ts | 15 +- .../entity-state-controller.component.html | 2 +- .../entity-state-controller.component.scss | 15 +- .../entity-state-controller.component.ts | 9 +- ...age-dashboard-states-dialog.component.html | 8 +- ...ashboard-states-dialog.component.models.ts | 2 +- ...age-dashboard-states-dialog.component.scss | 2 +- ...anage-dashboard-states-dialog.component.ts | 10 +- .../states/state-controller.component.ts | 4 +- .../states/state-controller.models.ts | 2 +- .../states/states-component.directive.ts | 4 +- .../states/states-controller.module.ts | 2 +- .../states/states-controller.service.ts | 2 +- .../widget-types-panel.component.html | 2 +- .../widget-types-panel.component.scss | 2 +- .../widget-types-panel.component.ts | 2 +- .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.scss | 2 +- .../dashboard/dashboard.component.ts | 2 +- .../components/dashboard/layout-button.scss | 13 +- ...select-target-layout-dialog.component.html | 2 +- .../select-target-layout-dialog.component.ts | 2 +- .../select-target-state-dialog.component.html | 2 +- .../select-target-state-dialog.component.ts | 10 +- .../components/details-panel.component.html | 8 +- .../components/details-panel.component.scss | 2 +- .../components/details-panel.component.ts | 20 +- .../copy-device-credentials.component.html | 2 +- .../copy-device-credentials.component.ts | 2 +- ...ce-credentials-lwm2m-server.component.html | 2 +- ...vice-credentials-lwm2m-server.component.ts | 14 +- .../device-credentials-lwm2m.component.html | 4 +- .../device-credentials-lwm2m.component.scss | 8 +- .../device-credentials-lwm2m.component.ts | 14 +- ...vice-credentials-mqtt-basic.component.html | 2 +- ...device-credentials-mqtt-basic.component.ts | 14 +- .../device/device-credentials.component.html | 2 +- .../device/device-credentials.component.ts | 16 +- .../device/device-credentials.module.ts | 2 +- .../edge/edge-downlink-table-config.ts | 2 +- .../edge-downlink-table-header.component.html | 2 +- .../edge-downlink-table-header.component.scss | 23 +- .../edge-downlink-table-header.component.ts | 2 +- .../edge/edge-downlink-table.component.html | 2 +- .../edge/edge-downlink-table.component.scss | 2 +- .../edge/edge-downlink-table.component.ts | 2 +- .../entity/add-entity-dialog.component.html | 2 +- .../entity/add-entity-dialog.component.scss | 2 +- .../entity/add-entity-dialog.component.ts | 8 +- .../entity/contact-based.component.ts | 10 +- .../entity/entities-table.component.html | 24 +- .../entity/entities-table.component.scss | 10 +- .../entity/entities-table.component.ts | 22 +- .../entity/entity-details-page.component.html | 14 +- .../entity/entity-details-page.component.scss | 15 +- .../entity/entity-details-page.component.ts | 12 +- .../entity-details-panel.component.html | 4 +- .../entity-details-panel.component.scss | 10 +- .../entity/entity-details-panel.component.ts | 6 +- .../entity/entity-filter-view.component.html | 2 +- .../entity/entity-filter-view.component.scss | 2 +- .../entity/entity-filter-view.component.ts | 2 +- .../entity/entity-filter.component.html | 10 +- .../entity/entity-filter.component.scss | 2 +- .../entity/entity-filter.component.ts | 10 +- .../entity/entity-table-header.component.ts | 4 +- .../entity/entity-tabs.component.ts | 8 +- .../components/entity/entity.component.ts | 12 +- .../event/event-content-dialog.component.html | 4 +- .../event/event-content-dialog.component.scss | 2 +- .../event/event-content-dialog.component.ts | 2 +- .../event/event-filter-panel.component.html | 2 +- .../event/event-filter-panel.component.scss | 2 +- .../event/event-filter-panel.component.ts | 8 +- .../components/event/event-table-config.ts | 4 +- .../event/event-table-header.component.html | 2 +- .../event/event-table-header.component.scss | 24 +- .../event/event-table-header.component.ts | 2 +- .../event/event-table.component.html | 2 +- .../event/event-table.component.scss | 2 +- .../components/event/event-table.component.ts | 2 +- .../boolean-filter-predicate.component.html | 5 +- .../boolean-filter-predicate.component.ts | 10 +- ...lex-filter-predicate-dialog.component.html | 4 +- ...mplex-filter-predicate-dialog.component.ts | 10 +- .../complex-filter-predicate.component.html | 2 +- .../complex-filter-predicate.component.ts | 2 +- .../filter/filter-component.models.ts | 2 +- .../filter/filter-dialog.component.html | 2 +- .../filter/filter-dialog.component.scss | 2 +- .../filter/filter-dialog.component.ts | 16 +- .../filter-predicate-list.component.html | 10 +- .../filter-predicate-list.component.scss | 7 +- .../filter/filter-predicate-list.component.ts | 75 +- .../filter-predicate-value.component.html | 14 +- .../filter-predicate-value.component.ts | 10 +- .../filter/filter-predicate.component.html | 2 +- .../filter/filter-predicate.component.ts | 10 +- .../components/filter/filter-predicate.scss | 20 +- .../filter/filter-select.component.html | 9 +- .../filter/filter-select.component.models.ts | 2 +- .../filter/filter-select.component.ts | 16 +- .../filter/filter-text.component.html | 2 +- .../filter/filter-text.component.scss | 2 +- .../filter/filter-text.component.ts | 6 +- .../filter-user-info-dialog.component.html | 2 +- .../filter-user-info-dialog.component.ts | 10 +- .../filter/filter-user-info.component.html | 2 +- .../filter/filter-user-info.component.ts | 2 +- .../filter/filters-dialog.component.html | 11 +- .../filter/filters-dialog.component.scss | 4 +- .../filter/filters-dialog.component.ts | 58 +- .../filter/filters-edit-panel.component.html | 2 +- .../filter/filters-edit-panel.component.scss | 2 +- .../filter/filters-edit-panel.component.ts | 2 +- .../filter/filters-edit.component.html | 2 +- .../filter/filters-edit.component.scss | 2 +- .../filter/filters-edit.component.ts | 2 +- .../filter/key-filter-dialog.component.html | 8 +- .../filter/key-filter-dialog.component.scss | 14 +- .../filter/key-filter-dialog.component.ts | 12 +- .../filter/key-filter-list.component.html | 8 +- .../filter/key-filter-list.component.scss | 4 +- .../filter/key-filter-list.component.ts | 83 +- .../numeric-filter-predicate.component.html | 5 +- .../numeric-filter-predicate.component.ts | 10 +- .../string-filter-predicate.component.html | 5 +- .../string-filter-predicate.component.ts | 10 +- .../filter/user-filter-dialog.component.html | 2 +- .../filter/user-filter-dialog.component.ts | 20 +- .../home/components/home-components.module.ts | 20 +- .../import-dialog-csv.component.html | 10 +- .../import-dialog-csv.component.scss | 4 +- .../import-dialog-csv.component.ts | 16 +- .../import-dialog.component.html | 4 +- .../import-export/import-dialog.component.ts | 10 +- .../import-export/import-export.models.ts | 7 +- .../import-export/import-export.service.ts | 32 +- .../table-columns-assignment.component.html | 6 +- .../table-columns-assignment.component.scss | 2 +- .../table-columns-assignment.component.ts | 6 +- .../notification-bell.component.html | 20 + .../notification-bell.component.ts | 111 + .../send-notification-button.component.html | 26 + .../send-notification-button.component.ts | 73 + .../show-notification-popover.component.html | 47 + .../show-notification-popover.component.ts | 101 + .../add-device-profile-dialog.component.html | 230 +- .../add-device-profile-dialog.component.scss | 41 +- .../add-device-profile-dialog.component.ts | 41 +- ...rm-duration-predicate-value.component.html | 11 +- ...larm-duration-predicate-value.component.ts | 10 +- .../alarm/alarm-dynamic-value.component.html | 8 +- .../alarm/alarm-dynamic-value.component.ts | 10 +- ...alarm-rule-condition-dialog.component.html | 7 +- ...alarm-rule-condition-dialog.component.scss | 2 +- .../alarm-rule-condition-dialog.component.ts | 10 +- .../alarm/alarm-rule-condition.component.html | 2 +- .../alarm/alarm-rule-condition.component.scss | 4 +- .../alarm/alarm-rule-condition.component.ts | 14 +- .../profile/alarm/alarm-rule.component.html | 6 +- .../profile/alarm/alarm-rule.component.scss | 4 +- .../profile/alarm/alarm-rule.component.ts | 20 +- .../alarm-schedule-dialog.component.html | 14 +- .../alarm/alarm-schedule-dialog.component.ts | 10 +- .../alarm/alarm-schedule-info.component.html | 2 +- .../alarm/alarm-schedule-info.component.scss | 4 +- .../alarm/alarm-schedule-info.component.ts | 2 +- .../alarm/alarm-schedule.component.html | 11 +- .../alarm/alarm-schedule.component.scss | 2 +- .../profile/alarm/alarm-schedule.component.ts | 30 +- .../alarm/create-alarm-rules.component.html | 2 +- .../alarm/create-alarm-rules.component.scss | 6 +- .../alarm/create-alarm-rules.component.ts | 64 +- .../alarm/device-profile-alarm.component.html | 18 +- .../alarm/device-profile-alarm.component.scss | 2 +- .../alarm/device-profile-alarm.component.ts | 20 +- .../device-profile-alarms.component.html | 6 +- .../device-profile-alarms.component.scss | 2 +- .../alarm/device-profile-alarms.component.ts | 74 +- .../edit-alarm-details-dialog.component.html | 2 +- .../edit-alarm-details-dialog.component.ts | 10 +- .../asset-profile-autocomplete.component.html | 11 +- .../asset-profile-autocomplete.component.scss | 4 +- .../asset-profile-autocomplete.component.ts | 12 +- .../asset-profile-dialog.component.html | 2 +- .../profile/asset-profile-dialog.component.ts | 6 +- .../profile/asset-profile.component.html | 12 +- .../profile/asset-profile.component.ts | 16 +- ...device-profile-autocomplete.component.html | 11 +- ...device-profile-autocomplete.component.scss | 4 +- .../device-profile-autocomplete.component.ts | 12 +- .../device-profile-dialog.component.html | 2 +- .../device-profile-dialog.component.ts | 6 +- ...ile-provision-configuration.component.html | 6 +- ...ofile-provision-configuration.component.ts | 14 +- .../profile/device-profile.component.html | 12 +- .../profile/device-profile.component.ts | 22 +- ...ile-transport-configuration.component.html | 2 +- ...ile-transport-configuration.component.scss | 2 +- ...ofile-transport-configuration.component.ts | 16 +- .../common/device-profile-common.module.ts | 2 +- .../common/power-mode-setting.component.html | 4 +- .../common/power-mode-setting.component.ts | 8 +- .../common/time-unit-select.component.html | 2 +- .../common/time-unit-select.component.ts | 12 +- ...evice-profile-configuration.component.html | 2 +- ...-device-profile-configuration.component.ts | 8 +- ...ile-transport-configuration.component.html | 2 +- ...ofile-transport-configuration.component.ts | 8 +- ...evice-profile-configuration.component.html | 2 +- .../device-profile-configuration.component.ts | 10 +- ...ile-transport-configuration.component.html | 2 +- ...ofile-transport-configuration.component.ts | 8 +- .../lwm2m-attributes-dialog.component.html | 2 +- .../lwm2m-attributes-dialog.component.ts | 10 +- .../lwm2m-attributes-key-list.component.html | 8 +- .../lwm2m-attributes-key-list.component.scss | 35 +- .../lwm2m-attributes-key-list.component.ts | 37 +- .../lwm2m/lwm2m-attributes.component.html | 4 +- .../lwm2m/lwm2m-attributes.component.ts | 10 +- ...ap-add-config-server-dialog.component.html | 4 +- ...trap-add-config-server-dialog.component.ts | 8 +- ...2m-bootstrap-config-servers.component.html | 6 +- ...wm2m-bootstrap-config-servers.component.ts | 79 +- .../lwm2m-device-config-server.component.html | 2 +- .../lwm2m-device-config-server.component.ts | 12 +- ...ile-transport-configuration.component.html | 2 +- ...ile-transport-configuration.component.scss | 4 +- ...ofile-transport-configuration.component.ts | 21 +- ...object-add-instances-dialog.component.html | 4 +- ...m-object-add-instances-dialog.component.ts | 8 +- ...m-object-add-instances-list.component.html | 11 +- ...m2m-object-add-instances-list.component.ts | 10 +- .../lwm2m/lwm2m-object-list.component.html | 13 +- .../lwm2m/lwm2m-object-list.component.ts | 10 +- ...ve-attr-telemetry-instances.component.html | 2 +- ...ve-attr-telemetry-instances.component.scss | 2 +- ...erve-attr-telemetry-instances.component.ts | 28 +- ...ve-attr-telemetry-resources.component.html | 5 +- ...ve-attr-telemetry-resources.component.scss | 24 +- ...erve-attr-telemetry-resources.component.ts | 40 +- ...wm2m-observe-attr-telemetry.component.html | 4 +- ...wm2m-observe-attr-telemetry.component.scss | 2 +- .../lwm2m-observe-attr-telemetry.component.ts | 28 +- .../lwm2m/lwm2m-profile-components.module.ts | 2 +- .../lwm2m/lwm2m-profile-config.models.ts | 2 +- ...ile-transport-configuration.component.html | 35 +- ...ile-transport-configuration.component.scss | 2 +- ...ofile-transport-configuration.component.ts | 42 +- ...rofile-communication-config.component.html | 6 +- ...rofile-communication-config.component.scss | 6 +- ...-profile-communication-config.component.ts | 73 +- ...snmp-device-profile-mapping.component.html | 15 +- ...snmp-device-profile-mapping.component.scss | 24 +- .../snmp-device-profile-mapping.component.ts | 71 +- ...ile-transport-configuration.component.html | 2 +- ...ofile-transport-configuration.component.ts | 12 +- .../snmp-device-profile-transport.module.ts | 2 +- .../tenant-profile-queues.component.html | 2 +- .../tenant-profile-queues.component.scss | 3 +- .../queue/tenant-profile-queues.component.ts | 83 +- ...tenant-profile-autocomplete.component.html | 9 +- ...tenant-profile-autocomplete.component.scss | 4 +- .../tenant-profile-autocomplete.component.ts | 8 +- .../tenant-profile-data.component.html | 2 +- .../profile/tenant-profile-data.component.ts | 8 +- .../tenant-profile-dialog.component.html | 2 +- .../tenant-profile-dialog.component.ts | 6 +- .../profile/tenant-profile.component.html | 2 +- .../profile/tenant-profile.component.scss | 16 +- .../profile/tenant-profile.component.ts | 8 +- ...enant-profile-configuration.component.html | 7 +- ...enant-profile-configuration.component.scss | 3 +- ...-tenant-profile-configuration.component.ts | 9 +- .../rate-limits-details-dialog.component.html | 2 +- .../rate-limits-details-dialog.component.ts | 8 +- .../rate-limits-list.component.html | 2 +- .../rate-limits-list.component.scss | 13 +- .../rate-limits/rate-limits-list.component.ts | 22 +- .../rate-limits-text.component.html | 2 +- .../rate-limits-text.component.scss | 2 +- .../rate-limits/rate-limits-text.component.ts | 2 +- .../rate-limits/rate-limits.component.html | 2 +- .../rate-limits/rate-limits.component.scss | 2 +- .../rate-limits/rate-limits.component.ts | 14 +- .../tenant/rate-limits/rate-limits.models.ts | 11 +- ...enant-profile-configuration.component.html | 2 +- .../tenant-profile-configuration.component.ts | 8 +- .../app/modules/home/components/public-api.ts | 2 +- .../queue/queue-form.component.html | 9 +- .../queue/queue-form.component.scss | 9 +- .../components/queue/queue-form.component.ts | 22 +- .../relation/relation-dialog.component.html | 4 +- .../relation/relation-dialog.component.scss | 2 +- .../relation/relation-dialog.component.ts | 14 +- .../relation/relation-filters.component.html | 15 +- .../relation/relation-filters.component.scss | 24 +- .../relation/relation-filters.component.ts | 70 +- .../relation/relation-table.component.html | 10 +- .../relation/relation-table.component.scss | 35 +- .../relation/relation-table.component.ts | 2 +- .../components/router-tabs.component.html | 37 + .../router-tabs.component.scss} | 51 +- .../home/components/router-tabs.component.ts | 125 + .../rule-chain-autocomplete.component.html | 10 +- .../rule-chain-autocomplete.component.ts | 18 +- .../shared-home-components.module.ts | 15 +- ...-sns-provider-configuration.component.html | 2 +- ...ws-sns-provider-configuration.component.ts | 8 +- ...-sms-provider-configuration.component.html | 6 +- ...pp-sms-provider-configuration.component.ts | 8 +- .../sms-provider-configuration.component.html | 2 +- .../sms-provider-configuration.component.ts | 8 +- ...-sms-provider-configuration.component.html | 4 +- ...io-sms-provider-configuration.component.ts | 8 +- .../src/app/modules/home/components/tokens.ts | 2 +- .../vc/auto-commit-settings.component.html | 18 +- .../vc/auto-commit-settings.component.scss | 4 +- .../vc/auto-commit-settings.component.ts | 22 +- .../vc/complex-version-create.component.html | 7 +- .../vc/complex-version-create.component.ts | 8 +- .../vc/complex-version-load.component.html | 11 +- .../vc/complex-version-load.component.ts | 8 +- ...entity-types-version-create.component.html | 4 +- .../entity-types-version-create.component.ts | 30 +- .../entity-types-version-load.component.html | 6 +- .../vc/entity-types-version-load.component.ts | 37 +- .../vc/entity-types-version.component.scss | 10 +- .../vc/entity-version-create.component.html | 7 +- .../vc/entity-version-create.component.ts | 8 +- .../vc/entity-version-diff.component.html | 2 +- .../vc/entity-version-diff.component.scss | 3 +- .../vc/entity-version-diff.component.ts | 2 +- .../vc/entity-version-restore.component.html | 7 +- .../vc/entity-version-restore.component.ts | 8 +- .../vc/entity-versions-table.component.html | 12 +- .../vc/entity-versions-table.component.scss | 37 +- .../vc/entity-versions-table.component.ts | 2 +- ...move-other-entities-confirm.component.html | 2 +- ...remove-other-entities-confirm.component.ts | 8 +- .../vc/repository-settings.component.html | 31 +- .../vc/repository-settings.component.scss | 12 +- .../vc/repository-settings.component.ts | 8 +- .../vc/version-control.component.html | 2 +- .../vc/version-control.component.scss | 2 +- .../vc/version-control.component.ts | 6 +- .../home/components/vc/version-control.scss | 2 +- ...custom-action-pretty-editor.component.html | 18 +- ...custom-action-pretty-editor.component.scss | 16 +- .../custom-action-pretty-editor.component.ts | 4 +- ...ction-pretty-resources-tabs.component.html | 17 +- ...ction-pretty-resources-tabs.component.scss | 26 +- ...-action-pretty-resources-tabs.component.ts | 2 +- .../widget/action/custom-action.models.ts | 2 +- .../manage-widget-actions.component.html | 8 +- .../manage-widget-actions.component.models.ts | 2 +- .../manage-widget-actions.component.scss | 9 +- .../action/manage-widget-actions.component.ts | 2 +- .../mobile-action-editor.component.html | 2 +- .../action/mobile-action-editor.component.ts | 13 +- .../action/mobile-action-editor.models.ts | 2 +- .../widget-action-dialog.component.html | 6 +- .../action/widget-action-dialog.component.ts | 22 +- .../data-key-config-dialog.component.html | 6 +- .../data-key-config-dialog.component.scss | 4 +- .../data-key-config-dialog.component.ts | 10 +- .../widget/data-key-config.component.html | 7 +- .../widget/data-key-config.component.scss | 12 +- .../widget/data-key-config.component.ts | 22 +- .../widget/data-keys.component.html | 246 +- .../widget/data-keys.component.models.ts | 2 +- .../widget/data-keys.component.scss | 133 +- .../components/widget/data-keys.component.ts | 104 +- .../custom-dialog-container.component.ts | 2 +- .../widget/dialog/custom-dialog.component.ts | 8 +- .../widget/dialog/custom-dialog.service.ts | 2 +- .../dialog/embed-dashboard-dialog-token.ts | 2 +- .../embed-dashboard-dialog.component.html | 2 +- .../embed-dashboard-dialog.component.scss | 4 +- .../embed-dashboard-dialog.component.ts | 2 +- .../widget/dynamic-widget.component.ts | 12 +- .../widget/legend-config.component.html | 2 +- .../widget/legend-config.component.ts | 8 +- .../components/widget/legend.component.html | 2 +- .../components/widget/legend.component.scss | 2 +- .../components/widget/legend.component.ts | 2 +- .../lib/alarm-filter-panel.component.html | 10 +- .../lib/alarm-filter-panel.component.scss | 2 +- .../lib/alarm-filter-panel.component.ts | 10 +- .../lib/alarms-table-widget.component.html | 59 +- .../lib/alarms-table-widget.component.scss | 44 +- .../lib/alarms-table-widget.component.ts | 145 +- .../widget/lib/analogue-compass.models.ts | 2 +- .../components/widget/lib/analogue-compass.ts | 2 +- .../widget/lib/analogue-gauge.models.ts | 2 +- .../lib/analogue-linear-gauge.models.ts | 2 +- .../widget/lib/analogue-linear-gauge.ts | 2 +- .../lib/analogue-radial-gauge.models.ts | 2 +- .../widget/lib/analogue-radial-gauge.ts | 2 +- .../widget/lib/canvas-digital-gauge.ts | 6 +- .../date-range-navigator-panel.component.html | 2 +- .../date-range-navigator-panel.component.scss | 2 +- .../date-range-navigator.component.html | 6 +- .../date-range-navigator.component.scss | 2 +- .../date-range-navigator.component.ts | 2 +- .../date-range-navigator.models.ts | 2 +- .../widget/lib/digital-gauge.models.ts | 2 +- .../components/widget/lib/digital-gauge.ts | 4 +- .../lib/display-columns-panel.component.html | 4 +- .../lib/display-columns-panel.component.scss | 2 +- .../lib/display-columns-panel.component.ts | 2 +- .../lib/edges-overview-widget.component.html | 2 +- .../lib/edges-overview-widget.component.scss | 6 +- .../lib/edges-overview-widget.component.ts | 2 +- .../lib/edges-overview-widget.models.ts | 2 +- .../entities-hierarchy-widget.component.html | 8 +- .../entities-hierarchy-widget.component.scss | 4 +- .../entities-hierarchy-widget.component.ts | 2 +- .../lib/entities-hierarchy-widget.models.ts | 2 +- .../lib/entities-table-widget.component.html | 24 +- .../lib/entities-table-widget.component.scss | 6 +- .../lib/entities-table-widget.component.ts | 2 +- .../widget/lib/flot-widget.models.ts | 4 +- .../home/components/widget/lib/flot-widget.ts | 3 +- .../lib/gateway/gateway-form.component.html | 4 +- .../lib/gateway/gateway-form.component.scss | 6 +- .../lib/gateway/gateway-form.component.ts | 12 +- .../widget/lib/gateway/gateway-form.models.ts | 2 +- .../lib/json-input-widget.component.html | 2 +- .../lib/json-input-widget.component.scss | 2 +- .../widget/lib/json-input-widget.component.ts | 8 +- .../home/components/widget/lib/maps/circle.ts | 2 +- .../widget/lib/maps/common-maps-utils.ts | 2 +- .../select-entity-dialog.component.html | 2 +- .../select-entity-dialog.component.scss | 2 +- .../dialogs/select-entity-dialog.component.ts | 8 +- .../components/widget/lib/maps/leaflet-map.ts | 66 +- .../components/widget/lib/maps/map-models.ts | 14 +- .../widget/lib/maps/map-widget.interface.ts | 2 +- .../components/widget/lib/maps/map-widget2.ts | 5 +- .../components/widget/lib/maps/maps-utils.ts | 2 +- .../components/widget/lib/maps/markers.scss | 2 +- .../components/widget/lib/maps/markers.ts | 2 +- .../components/widget/lib/maps/polygon.ts | 2 +- .../components/widget/lib/maps/polyline.ts | 2 +- .../widget/lib/maps/providers/google-map.ts | 2 +- .../widget/lib/maps/providers/here-map.ts | 9 +- .../widget/lib/maps/providers/image-map.ts | 39 +- .../widget/lib/maps/providers/index.ts | 2 +- .../lib/maps/providers/openstreet-map.ts | 2 +- .../widget/lib/maps/providers/tencent-map.ts | 2 +- .../widget/lib/markdown-widget.component.html | 5 +- .../widget/lib/markdown-widget.component.ts | 35 +- .../lib/multiple-input-widget.component.html | 4 +- .../lib/multiple-input-widget.component.scss | 34 +- .../lib/multiple-input-widget.component.ts | 10 +- .../lib/navigation-card-widget.component.html | 2 +- .../lib/navigation-card-widget.component.scss | 43 +- .../lib/navigation-card-widget.component.ts | 2 +- .../navigation-cards-widget.component.html | 6 +- .../navigation-cards-widget.component.scss | 51 +- .../lib/navigation-cards-widget.component.ts | 2 +- .../lib/photo-camera-input.component.html | 2 +- .../lib/photo-camera-input.component.scss | 4 +- .../lib/photo-camera-input.component.ts | 2 +- .../widget/lib/qrcode-widget.component.html | 2 +- .../widget/lib/qrcode-widget.component.ts | 2 +- .../widget/lib/rpc/knob.component.html | 2 +- .../widget/lib/rpc/knob.component.scss | 2 +- .../widget/lib/rpc/knob.component.ts | 2 +- .../lib/rpc/led-indicator.component.html | 2 +- .../lib/rpc/led-indicator.component.scss | 2 +- .../widget/lib/rpc/led-indicator.component.ts | 2 +- .../rpc/persistent-add-dialog.component.html | 2 +- .../rpc/persistent-add-dialog.component.scss | 2 +- .../rpc/persistent-add-dialog.component.ts | 8 +- .../persistent-details-dialog.component.html | 2 +- .../persistent-details-dialog.component.scss | 2 +- .../persistent-details-dialog.component.ts | 8 +- .../persistent-filter-panel.component.html | 2 +- .../persistent-filter-panel.component.scss | 2 +- .../rpc/persistent-filter-panel.component.ts | 8 +- .../lib/rpc/persistent-table.component.html | 6 +- .../lib/rpc/persistent-table.component.scss | 6 +- .../lib/rpc/persistent-table.component.ts | 2 +- .../lib/rpc/round-switch.component.html | 2 +- .../lib/rpc/round-switch.component.scss | 2 +- .../widget/lib/rpc/round-switch.component.ts | 2 +- .../widget/lib/rpc/rpc-widgets.module.ts | 2 +- .../widget/lib/rpc/switch.component.html | 2 +- .../widget/lib/rpc/switch.component.scss | 97 +- .../widget/lib/rpc/switch.component.ts | 4 +- .../components/widget/lib/settings.models.ts | 2 +- .../alarms-table-key-settings.component.html | 10 +- .../alarms-table-key-settings.component.ts | 10 +- ...larms-table-widget-settings.component.html | 20 +- .../alarms-table-widget-settings.component.ts | 13 +- ...board-state-widget-settings.component.html | 2 +- ...shboard-state-widget-settings.component.ts | 10 +- ...ck-overview-widget-settings.component.html | 2 +- ...uick-overview-widget-settings.component.ts | 10 +- ...s-hierarchy-widget-settings.component.html | 2 +- ...ies-hierarchy-widget-settings.component.ts | 10 +- ...entities-table-key-settings.component.html | 10 +- .../entities-table-key-settings.component.ts | 10 +- ...ities-table-widget-settings.component.html | 12 +- ...ntities-table-widget-settings.component.ts | 10 +- .../html-card-widget-settings.component.html | 2 +- .../html-card-widget-settings.component.ts | 10 +- .../cards/label-widget-label.component.html | 4 +- .../cards/label-widget-label.component.scss | 2 +- .../cards/label-widget-label.component.ts | 8 +- .../label-widget-settings.component.html | 2 +- .../cards/label-widget-settings.component.ts | 24 +- .../markdown-widget-settings.component.html | 7 +- .../markdown-widget-settings.component.ts | 12 +- .../qrcode-widget-settings.component.html | 2 +- .../cards/qrcode-widget-settings.component.ts | 10 +- ...simple-card-widget-settings.component.html | 2 +- .../simple-card-widget-settings.component.ts | 10 +- ...meseries-table-key-settings.component.html | 2 +- ...timeseries-table-key-settings.component.ts | 10 +- ...s-table-latest-key-settings.component.html | 2 +- ...ies-table-latest-key-settings.component.ts | 10 +- ...eries-table-widget-settings.component.html | 8 +- ...eseries-table-widget-settings.component.ts | 10 +- .../chart-widget-settings.component.html | 2 +- .../chart/chart-widget-settings.component.ts | 10 +- ...ghnut-chart-widget-settings.component.html | 4 +- ...oughnut-chart-widget-settings.component.ts | 10 +- .../flot-bar-key-settings.component.html | 3 +- .../chart/flot-bar-key-settings.component.ts | 10 +- .../flot-bar-widget-settings.component.html | 3 +- .../flot-bar-widget-settings.component.ts | 10 +- .../chart/flot-key-settings.component.html | 20 +- .../chart/flot-key-settings.component.ts | 26 +- .../flot-latest-key-settings.component.html | 2 +- .../flot-latest-key-settings.component.ts | 10 +- .../flot-line-key-settings.component.html | 3 +- .../chart/flot-line-key-settings.component.ts | 10 +- .../flot-line-widget-settings.component.html | 3 +- .../flot-line-widget-settings.component.ts | 10 +- .../flot-pie-key-settings.component.html | 8 +- .../chart/flot-pie-key-settings.component.ts | 10 +- .../flot-pie-widget-settings.component.html | 6 +- .../flot-pie-widget-settings.component.ts | 10 +- .../chart/flot-threshold.component.html | 2 +- .../chart/flot-threshold.component.scss | 2 +- .../chart/flot-threshold.component.ts | 8 +- .../chart/flot-widget-settings.component.html | 14 +- .../chart/flot-widget-settings.component.ts | 26 +- .../chart/label-data-key.component.html | 2 +- .../chart/label-data-key.component.scss | 2 +- .../chart/label-data-key.component.ts | 10 +- .../common/value-source.component.html | 2 +- .../settings/common/value-source.component.ts | 8 +- .../common/widget-font.component.html | 8 +- .../settings/common/widget-font.component.ts | 8 +- .../device-key-autocomplete.component.html | 2 +- .../device-key-autocomplete.component.ts | 8 +- ...nob-control-widget-settings.component.html | 5 +- .../knob-control-widget-settings.component.ts | 10 +- ...d-indicator-widget-settings.component.html | 5 +- ...led-indicator-widget-settings.component.ts | 10 +- ...stent-table-widget-settings.component.html | 18 +- ...sistent-table-widget-settings.component.ts | 14 +- ...ound-switch-widget-settings.component.html | 3 +- .../round-switch-widget-settings.component.ts | 10 +- .../control/rpc-button-style.component.html | 4 +- .../control/rpc-button-style.component.ts | 8 +- .../rpc-shell-widget-settings.component.html | 3 +- .../rpc-shell-widget-settings.component.ts | 10 +- ...pc-terminal-widget-settings.component.html | 3 +- .../rpc-terminal-widget-settings.component.ts | 10 +- .../send-rpc-widget-settings.component.html | 7 +- .../send-rpc-widget-settings.component.ts | 10 +- ...lide-toggle-widget-settings.component.html | 5 +- .../slide-toggle-widget-settings.component.ts | 10 +- ...tch-control-widget-settings.component.html | 3 +- ...witch-control-widget-settings.component.ts | 10 +- .../switch-rpc-settings.component.html | 4 +- .../control/switch-rpc-settings.component.ts | 14 +- ...e-attribute-widget-settings.component.html | 3 +- ...ice-attribute-widget-settings.component.ts | 10 +- ...e-navigator-widget-settings.component.html | 8 +- ...nge-navigator-widget-settings.component.ts | 10 +- ...ngle-device-widget-settings.component.html | 2 +- ...single-device-widget-settings.component.ts | 10 +- ...eway-config-widget-settings.component.html | 2 +- ...ateway-config-widget-settings.component.ts | 10 +- ...eway-events-widget-settings.component.html | 10 +- ...ateway-events-widget-settings.component.ts | 10 +- ...gue-compass-widget-settings.component.html | 16 +- ...logue-compass-widget-settings.component.ts | 12 +- ...logue-gauge-widget-settings.component.html | 24 +- ...nalogue-gauge-widget-settings.component.ts | 24 +- ...-linear-gauge-widget-settings.component.ts | 6 +- ...-radial-gauge-widget-settings.component.ts | 6 +- ...gital-gauge-widget-settings.component.html | 10 +- ...digital-gauge-widget-settings.component.ts | 48 +- .../gauge/fixed-color-level.component.html | 2 +- .../gauge/fixed-color-level.component.scss | 2 +- .../gauge/fixed-color-level.component.ts | 10 +- .../gauge/gauge-highlight.component.html | 4 +- .../gauge/gauge-highlight.component.scss | 2 +- .../gauge/gauge-highlight.component.ts | 8 +- .../settings/gauge/tick-value.component.html | 2 +- .../settings/gauge/tick-value.component.scss | 2 +- .../settings/gauge/tick-value.component.ts | 8 +- ...pio-control-widget-settings.component.html | 2 +- .../gpio-control-widget-settings.component.ts | 22 +- .../settings/gpio/gpio-item.component.html | 13 +- .../settings/gpio/gpio-item.component.scss | 2 +- .../lib/settings/gpio/gpio-item.component.ts | 55 +- .../gpio-panel-widget-settings.component.html | 2 +- .../gpio-panel-widget-settings.component.ts | 22 +- .../datakey-select-option.component.html | 7 +- .../datakey-select-option.component.scss | 2 +- .../input/datakey-select-option.component.ts | 23 +- ...ce-claiming-widget-settings.component.html | 2 +- ...vice-claiming-widget-settings.component.ts | 10 +- ...amera-input-widget-settings.component.html | 2 +- ...-camera-input-widget-settings.component.ts | 10 +- ...-attribute-general-settings.component.html | 2 +- ...te-attribute-general-settings.component.ts | 14 +- ...n-attribute-widget-settings.component.html | 4 +- ...ean-attribute-widget-settings.component.ts | 10 +- ...e-attribute-widget-settings.component.html | 3 +- ...ate-attribute-widget-settings.component.ts | 10 +- ...e-attribute-widget-settings.component.html | 5 +- ...ble-attribute-widget-settings.component.ts | 10 +- ...e-attribute-widget-settings.component.html | 2 +- ...age-attribute-widget-settings.component.ts | 10 +- ...r-attribute-widget-settings.component.html | 5 +- ...ger-attribute-widget-settings.component.ts | 10 +- ...n-attribute-widget-settings.component.html | 4 +- ...son-attribute-widget-settings.component.ts | 10 +- ...n-attribute-widget-settings.component.html | 10 +- ...ion-attribute-widget-settings.component.ts | 10 +- ...ple-attributes-key-settings.component.html | 6 +- ...tiple-attributes-key-settings.component.ts | 24 +- ...-attributes-widget-settings.component.html | 4 +- ...le-attributes-widget-settings.component.ts | 10 +- ...g-attribute-widget-settings.component.html | 5 +- ...ing-attribute-widget-settings.component.ts | 10 +- .../map/circle-settings.component.html | 6 +- .../settings/map/circle-settings.component.ts | 14 +- .../map/common-map-settings.component.html | 8 +- .../map/common-map-settings.component.ts | 14 +- ...atasources-key-autocomplete.component.html | 2 +- .../datasources-key-autocomplete.component.ts | 8 +- ...oogle-map-provider-settings.component.html | 2 +- .../google-map-provider-settings.component.ts | 14 +- .../here-map-provider-settings.component.html | 30 +- .../here-map-provider-settings.component.ts | 25 +- ...image-map-provider-settings.component.html | 2 +- .../image-map-provider-settings.component.ts | 14 +- .../map/map-editor-settings.component.html | 6 +- .../map/map-editor-settings.component.ts | 14 +- .../map/map-provider-settings.component.html | 2 +- .../map/map-provider-settings.component.ts | 14 +- .../settings/map/map-settings.component.html | 2 +- .../settings/map/map-settings.component.ts | 14 +- .../map/map-widget-settings.component.html | 2 +- .../map/map-widget-settings.component.ts | 10 +- .../marker-clustering-settings.component.html | 14 +- .../marker-clustering-settings.component.ts | 14 +- .../map/markers-settings.component.html | 10 +- .../map/markers-settings.component.ts | 14 +- ...treet-map-provider-settings.component.html | 2 +- ...nstreet-map-provider-settings.component.ts | 14 +- .../map/polygon-settings.component.html | 6 +- .../map/polygon-settings.component.ts | 14 +- .../map/route-map-settings.component.html | 4 +- .../map/route-map-settings.component.ts | 14 +- .../route-map-widget-settings.component.html | 2 +- .../route-map-widget-settings.component.ts | 10 +- ...ncent-map-provider-settings.component.html | 2 +- ...tencent-map-provider-settings.component.ts | 14 +- ...p-animation-common-settings.component.html | 2 +- ...rip-animation-common-settings.component.ts | 14 +- ...p-animation-marker-settings.component.html | 2 +- ...rip-animation-marker-settings.component.ts | 14 +- ...rip-animation-path-settings.component.html | 6 +- .../trip-animation-path-settings.component.ts | 14 +- ...ip-animation-point-settings.component.html | 4 +- ...trip-animation-point-settings.component.ts | 14 +- ...p-animation-widget-settings.component.html | 2 +- ...rip-animation-widget-settings.component.ts | 10 +- ...gation-card-widget-settings.component.html | 2 +- ...vigation-card-widget-settings.component.ts | 10 +- ...ation-cards-widget-settings.component.html | 10 +- ...igation-cards-widget-settings.component.ts | 16 +- .../lib/settings/widget-settings.module.ts | 2 +- .../widget/lib/settings/widget-settings.scss | 34 +- .../widget/lib/table-widget.models.ts | 67 +- .../components/widget/lib/table-widget.scss | 8 +- .../timeseries-table-widget.component.html | 25 +- .../timeseries-table-widget.component.scss | 8 +- .../lib/timeseries-table-widget.component.ts | 4 +- .../trip-animation.component.html | 2 +- .../trip-animation.component.scss | 7 +- .../trip-animation.component.ts | 4 +- .../widget/widget-component.service.ts | 6 +- .../widget/widget-components.module.ts | 2 +- .../widget/widget-config.component.html | 71 +- .../widget/widget-config.component.models.ts | 2 +- .../widget/widget-config.component.scss | 173 +- .../widget/widget-config.component.ts | 87 +- .../widget/widget-container.component.html | 16 +- .../widget/widget-container.component.scss | 17 +- .../widget/widget-container.component.ts | 20 +- .../widget/widget-settings.component.html | 2 +- .../widget/widget-settings.component.scss | 2 +- .../widget/widget-settings.component.ts | 8 +- .../components/widget/widget.component.html | 2 +- .../components/widget/widget.component.scss | 2 +- .../components/widget/widget.component.ts | 9 +- .../device-wizard-dialog.component.html | 334 +- .../device-wizard-dialog.component.scss | 47 +- .../wizard/device-wizard-dialog.component.ts | 26 +- ...entities-to-customer-dialog.component.html | 4 +- ...d-entities-to-customer-dialog.component.ts | 10 +- ...add-entities-to-edge-dialog.component.html | 4 +- .../add-entities-to-edge-dialog.component.ts | 10 +- .../assign-to-customer-dialog.component.html | 4 +- .../assign-to-customer-dialog.component.ts | 10 +- .../home/dialogs/home-dialogs.module.ts | 2 +- .../home/dialogs/home-dialogs.service.ts | 2 +- .../app/modules/home/home-routing.module.ts | 2 +- .../src/app/modules/home/home.component.html | 12 +- .../src/app/modules/home/home.component.scss | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 38 +- ui-ngx/src/app/modules/home/home.module.ts | 8 +- .../home/menu/menu-link.component.html | 8 +- .../home/menu/menu-link.component.scss | 2 +- .../modules/home/menu/menu-link.component.ts | 2 +- .../home/menu/menu-toggle.component.html | 11 +- .../home/menu/menu-toggle.component.scss | 7 +- .../home/menu/menu-toggle.component.ts | 22 +- .../home/menu/side-menu.component.html | 2 +- .../home/menu/side-menu.component.scss | 39 +- .../modules/home/menu/side-menu.component.ts | 2 +- .../app/modules/home/models/contact.models.ts | 6 +- .../home/models/dashboard-component.models.ts | 2 +- .../models/datasource/attribute-datasource.ts | 2 +- .../models/datasource/entity-datasource.ts | 2 +- .../models/datasource/relation-datasource.ts | 2 +- .../entity/entities-table-config.models.ts | 13 +- .../models/entity/entity-component.models.ts | 2 +- .../entity-details-page-component.models.ts | 2 +- .../entity/entity-table-component.models.ts | 3 +- .../models/searchable-component.models.ts | 2 +- .../app/modules/home/models/services.map.ts | 10 +- .../home/models/widget-component.models.ts | 12 +- .../home/pages/admin/admin-routing.module.ts | 267 +- .../modules/home/pages/admin/admin.module.ts | 4 +- .../auto-commit-admin-settings.component.html | 2 +- .../auto-commit-admin-settings.component.ts | 6 +- .../admin/general-settings.component.html | 22 +- .../admin/general-settings.component.scss | 7 +- .../pages/admin/general-settings.component.ts | 10 +- .../pages/admin/home-settings.component.html | 16 +- .../pages/admin/home-settings.component.scss | 4 +- .../pages/admin/home-settings.component.ts | 10 +- .../pages/admin/mail-server.component.html | 18 +- .../pages/admin/mail-server.component.scss | 2 +- .../home/pages/admin/mail-server.component.ts | 12 +- .../admin/oauth2-settings.component.html | 29 +- .../admin/oauth2-settings.component.scss | 8 +- .../pages/admin/oauth2-settings.component.ts | 56 +- .../pages/admin/queue/queue.component.html | 2 +- .../home/pages/admin/queue/queue.component.ts | 10 +- .../queue/queues-table-config.resolver.ts | 2 +- .../repository-admin-settings.component.html | 2 +- .../repository-admin-settings.component.ts | 6 +- .../resources-library-table-config.resolve.ts | 4 +- .../resource/resources-library.component.html | 2 +- .../resource/resources-library.component.ts | 10 +- .../admin/security-settings.component.html | 32 +- .../admin/security-settings.component.scss | 10 +- .../admin/security-settings.component.ts | 16 +- .../admin/send-test-sms-dialog.component.html | 4 +- .../admin/send-test-sms-dialog.component.ts | 8 +- .../home/pages/admin/settings-card.scss | 15 +- .../pages/admin/sms-provider.component.html | 91 +- .../pages/admin/sms-provider.component.scss | 2 +- .../pages/admin/sms-provider.component.ts | 94 +- .../two-factor-auth-settings.component.html | 22 +- .../two-factor-auth-settings.component.scss | 14 +- .../two-factor-auth-settings.component.ts | 18 +- .../home/pages/alarm/alarm-routing.module.ts | 58 + .../modules/home/pages/alarm/alarm.module.ts | 34 + .../api-usage/api-usage-routing.module.ts | 2 +- .../pages/api-usage/api-usage.component.html | 2 +- .../pages/api-usage/api-usage.component.scss | 2 +- .../pages/api-usage/api-usage.component.ts | 2 +- .../home/pages/api-usage/api-usage.module.ts | 2 +- .../home/pages/api-usage/api_usage_json.raw | 24 +- .../asset-profile-routing.module.ts | 2 +- .../asset-profile-tabs.component.html | 2 +- .../asset-profile-tabs.component.ts | 2 +- .../asset-profile/asset-profile.module.ts | 2 +- .../asset-profiles-table-config.resolver.ts | 2 +- .../home/pages/asset/asset-routing.module.ts | 16 +- .../asset/asset-table-header.component.html | 3 +- .../asset/asset-table-header.component.scss | 49 - .../asset/asset-table-header.component.ts | 4 +- .../pages/asset/asset-tabs.component.html | 2 +- .../home/pages/asset/asset-tabs.component.ts | 2 +- .../home/pages/asset/asset.component.html | 2 +- .../home/pages/asset/asset.component.scss | 2 +- .../home/pages/asset/asset.component.ts | 8 +- .../modules/home/pages/asset/asset.module.ts | 2 +- .../asset/assets-table-config.resolver.ts | 2 +- .../audit-log/audit-log-routing.module.ts | 14 +- .../home/pages/audit-log/audit-log.module.ts | 2 +- .../pages/customer/customer-routing.module.ts | 2 +- .../customer/customer-tabs.component.html | 2 +- .../pages/customer/customer-tabs.component.ts | 2 +- .../pages/customer/customer.component.html | 4 +- .../pages/customer/customer.component.scss | 4 +- .../home/pages/customer/customer.component.ts | 8 +- .../home/pages/customer/customer.module.ts | 2 +- .../customers-table-config.resolver.ts | 2 +- .../dashboard/dashboard-form.component.html | 8 +- .../dashboard/dashboard-form.component.scss | 2 +- .../dashboard/dashboard-form.component.ts | 8 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../dashboard/dashboard-tabs.component.html | 2 +- .../dashboard/dashboard-tabs.component.ts | 2 +- .../home/pages/dashboard/dashboard.module.ts | 2 +- .../dashboards-table-config.resolver.ts | 2 +- ...ake-dashboard-public-dialog.component.html | 6 +- .../make-dashboard-public-dialog.component.ts | 6 +- ...-dashboard-customers-dialog.component.html | 4 +- ...ge-dashboard-customers-dialog.component.ts | 10 +- .../device-profile-routing.module.ts | 2 +- .../device-profile-tabs.component.html | 2 +- .../device-profile-tabs.component.ts | 2 +- .../device-profile/device-profile.module.ts | 2 +- .../device-profiles-table-config.resolver.ts | 2 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 10 +- ...efault-device-configuration.component.html | 2 +- .../default-device-configuration.component.ts | 8 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 8 +- .../data/device-configuration.component.html | 2 +- .../data/device-configuration.component.ts | 8 +- .../device/data/device-data.component.html | 2 +- .../device/data/device-data.component.ts | 10 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 10 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 10 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 8 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 10 +- .../device-credentials-dialog.component.html | 4 +- .../device-credentials-dialog.component.ts | 17 +- .../pages/device/device-routing.module.ts | 18 +- .../device/device-table-header.component.html | 3 +- .../device/device-table-header.component.scss | 49 - .../device/device-table-header.component.ts | 4 +- .../pages/device/device-tabs.component.html | 2 +- .../pages/device/device-tabs.component.ts | 2 +- .../home/pages/device/device.component.html | 2 +- .../home/pages/device/device.component.scss | 2 +- .../home/pages/device/device.component.ts | 8 +- .../home/pages/device/device.module.ts | 2 +- .../device/devices-table-config.resolver.ts | 2 +- .../edge-instructions-dialog.component.html | 43 + .../edge-instructions-dialog.component.ts | 46 + .../home/pages/edge/edge-routing.module.ts | 439 +- .../edge/edge-table-header.component.html | 3 +- .../edge/edge-table-header.component.scss | 49 - .../pages/edge/edge-table-header.component.ts | 4 +- .../home/pages/edge/edge-tabs.component.html | 2 +- .../home/pages/edge/edge-tabs.component.ts | 2 +- .../home/pages/edge/edge.component.html | 15 +- .../home/pages/edge/edge.component.scss | 2 +- .../modules/home/pages/edge/edge.component.ts | 10 +- .../modules/home/pages/edge/edge.module.ts | 6 +- .../pages/edge/edges-table-config.resolver.ts | 30 +- .../pages/entities/entities-routing.module.ts | 53 + .../home/pages/entities/entities.module.ts | 30 + .../entity-view/entity-view-routing.module.ts | 16 +- .../entity-view-table-header.component.html | 3 +- .../entity-view-table-header.component.ts | 4 +- .../entity-view-tabs.component.html | 2 +- .../entity-view/entity-view-tabs.component.ts | 2 +- .../entity-view/entity-view.component.html | 2 +- .../entity-view/entity-view.component.scss | 2 +- .../entity-view/entity-view.component.ts | 8 +- .../pages/entity-view/entity-view.module.ts | 2 +- .../entity-views-table-config.resolver.ts | 2 +- .../pages/features/features-routing.module.ts | 52 + .../home/pages/features/features.module.ts | 30 + .../home-links/home-links-routing.module.ts | 2 +- .../home-links/home-links.component.html | 6 +- .../home-links/home-links.component.scss | 51 +- .../pages/home-links/home-links.component.ts | 2 +- .../pages/home-links/home-links.module.ts | 2 +- .../modules/home/pages/home-pages.models.ts | 4 +- .../modules/home/pages/home-pages.module.ts | 18 +- .../inbox-notification-dialog.component.html | 21 + .../inbox-notification-dialog.component.scss} | 15 +- .../inbox-notification-dialog.component.ts | 50 + .../inbox/inbox-table-config.resolver.ts | 165 + .../inbox/inbox-table-header.component.html | 27 + .../inbox/inbox-table-header.component.scss | 72 + .../inbox/inbox-table-header.component.ts | 39 + .../notification-routing.module.ts | 140 + .../pages/notification/notification.module.ts | 61 + ...cipient-notification-dialog.component.html | 139 + ...cipient-notification-dialog.component.scss | 109 + ...recipient-notification-dialog.component.ts | 210 + .../recipient-table-config.resolver.ts | 126 + .../recipient-table-header.component.html | 24 + .../recipient-table-header.component.scss} | 8 +- .../recipient-table-header.component.ts | 38 + .../rule/escalation-form.component.html | 48 + .../rule/escalation-form.component.scss | 66 + .../rule/escalation-form.component.ts | 173 + .../rule/escalations.component.html | 48 + .../rule/escalations.component.ts | 160 + .../rule-notification-dialog.component.html | 491 + .../rule-notification-dialog.component.scss | 177 + .../rule-notification-dialog.component.ts | 458 + .../rule/rule-table-config.resolver.ts | 131 + .../rule/rule-table-header.component.html | 24 + .../rule/rule-table-header.component.scss} | 9 +- .../rule/rule-table-header.component.ts | 38 + .../sent/sent-error-dialog.component.html | 40 + .../sent/sent-error-dialog.component.scss} | 46 +- .../sent/sent-error-dialog.component.ts | 56 + .../sent-notification-dialog.component.html | 363 + .../sent-notification-dialog.component.scss | 241 + .../sent/sent-notification-dialog.componet.ts | 275 + .../sent/sent-table-config.resolver.ts | 208 + .../template/template-configuration.ts | 201 + ...emplate-notification-dialog.component.html | 256 + ...emplate-notification-dialog.component.scss | 105 + .../template-notification-dialog.component.ts | 186 + .../template-table-config.resolver.ts | 130 + .../template-table-header.component.html | 24 + .../template-table-header.component.scss} | 10 +- .../template-table-header.component.ts | 38 + .../ota-update/ota-update-routing.module.ts | 16 +- .../ota-update-table-config.resolve.ts | 4 +- .../ota-update/ota-update.component.html | 5 +- .../pages/ota-update/ota-update.component.ts | 10 +- .../pages/ota-update/ota-update.module.ts | 2 +- .../pages/profile/profile-routing.module.ts | 2 +- .../home/pages/profile/profile.component.html | 23 +- .../home/pages/profile/profile.component.scss | 10 +- .../home/pages/profile/profile.component.ts | 12 +- .../home/pages/profile/profile.module.ts | 2 +- .../pages/profiles/profiles-routing.module.ts | 3 +- .../home/pages/profiles/profiles.module.ts | 2 +- .../src/app/modules/home/pages/public-api.ts | 2 +- .../add-rule-node-dialog.component.html | 2 +- .../add-rule-node-dialog.component.scss | 5 +- .../add-rule-node-link-dialog.component.html | 2 +- .../add-rule-node-link-dialog.component.scss | 2 +- ...ate-nested-rulechain-dialog.component.html | 2 +- .../rulechain/link-labels.component.html | 11 +- .../pages/rulechain/link-labels.component.ts | 12 +- .../pages/rulechain/rule-node-colors.scss | 2 +- .../rulechain/rule-node-config.component.html | 2 +- .../rulechain/rule-node-config.component.scss | 4 +- .../rulechain/rule-node-config.component.ts | 8 +- .../rule-node-details.component.html | 2 +- .../rule-node-details.component.scss | 2 +- .../rulechain/rule-node-details.component.ts | 8 +- .../rulechain/rule-node-link.component.html | 2 +- .../rulechain/rule-node-link.component.ts | 8 +- .../rulechain/rulechain-page.component.html | 26 +- .../rulechain/rulechain-page.component.scss | 47 +- .../rulechain/rulechain-page.component.ts | 61 +- .../pages/rulechain/rulechain-page.models.ts | 2 +- .../rulechain/rulechain-routing.module.ts | 2 +- .../rulechain/rulechain-tabs.component.html | 2 +- .../rulechain/rulechain-tabs.component.ts | 2 +- .../pages/rulechain/rulechain.component.html | 2 +- .../pages/rulechain/rulechain.component.scss | 2 +- .../pages/rulechain/rulechain.component.ts | 8 +- .../home/pages/rulechain/rulechain.module.ts | 2 +- .../rulechains-table-config.resolver.ts | 6 +- .../pages/rulechain/rulenode.component.html | 2 +- .../pages/rulechain/rulenode.component.scss | 2 +- .../pages/rulechain/rulenode.component.ts | 4 +- .../authentication-dialog.component.scss | 4 +- .../authentication-dialog.map.ts | 2 +- .../backup-code-auth-dialog.component.html | 6 +- .../backup-code-auth-dialog.component.ts | 6 +- .../email-auth-dialog.component.html | 8 +- .../email-auth-dialog.component.ts | 12 +- .../sms-auth-dialog.component.html | 7 +- .../sms-auth-dialog.component.ts | 12 +- .../totp-auth-dialog.component.html | 9 +- .../totp-auth-dialog.component.ts | 8 +- .../pages/security/security-routing.module.ts | 2 +- .../pages/security/security.component.html | 106 +- .../pages/security/security.component.scss | 16 +- .../home/pages/security/security.component.ts | 19 +- .../home/pages/security/security.module.ts | 2 +- .../tenant-profile-routing.module.ts | 2 +- .../tenant-profile-tabs.component.html | 7 +- .../tenant-profile-tabs.component.ts | 2 +- .../tenant-profile/tenant-profile.module.ts | 2 +- .../tenant-profiles-table-config.resolver.ts | 2 +- .../pages/tenant/tenant-routing.module.ts | 2 +- .../pages/tenant/tenant-tabs.component.html | 2 +- .../pages/tenant/tenant-tabs.component.ts | 2 +- .../home/pages/tenant/tenant.component.html | 4 +- .../home/pages/tenant/tenant.component.scss | 4 +- .../home/pages/tenant/tenant.component.ts | 8 +- .../home/pages/tenant/tenant.module.ts | 2 +- .../tenant/tenants-table-config.resolver.ts | 2 +- .../activation-link-dialog.component.html | 4 +- .../user/activation-link-dialog.component.ts | 2 +- .../pages/user/add-user-dialog.component.html | 2 +- .../pages/user/add-user-dialog.component.scss | 2 +- .../pages/user/add-user-dialog.component.ts | 6 +- .../home/pages/user/user-routing.module.ts | 2 +- .../home/pages/user/user-tabs.component.html | 2 +- .../home/pages/user/user-tabs.component.ts | 2 +- .../home/pages/user/user.component.html | 11 +- .../home/pages/user/user.component.scss | 4 +- .../modules/home/pages/user/user.component.ts | 12 +- .../modules/home/pages/user/user.module.ts | 2 +- .../pages/user/users-table-config.resolver.ts | 2 +- .../home/pages/vc/vc-routing.module.ts | 11 +- .../app/modules/home/pages/vc/vc.module.ts | 2 +- .../save-widget-type-as-dialog.component.html | 4 +- .../save-widget-type-as-dialog.component.ts | 8 +- .../select-widget-type-dialog.component.html | 2 +- .../select-widget-type-dialog.component.scss | 40 +- .../select-widget-type-dialog.component.ts | 2 +- .../pages/widget/widget-editor.component.html | 15 +- .../pages/widget/widget-editor.component.scss | 48 +- .../pages/widget/widget-editor.component.ts | 2 +- .../home/pages/widget/widget-editor.models.ts | 2 +- .../widget/widget-library-routing.module.ts | 30 +- .../widget/widget-library.component.html | 4 +- .../widget/widget-library.component.scss | 3 +- .../pages/widget/widget-library.component.ts | 2 +- .../pages/widget/widget-library.module.ts | 2 +- .../widget/widgets-bundle-tabs.component.html | 2 +- .../widget/widgets-bundle-tabs.component.ts | 2 +- .../widget/widgets-bundle.component.html | 2 +- .../widget/widgets-bundle.component.scss | 2 +- .../pages/widget/widgets-bundle.component.ts | 8 +- .../widgets-bundles-table-config.resolver.ts | 4 +- ui-ngx/src/app/modules/home/public-api.ts | 2 +- .../app/modules/login/login-routing.module.ts | 2 +- ui-ngx/src/app/modules/login/login.module.ts | 2 +- .../login/create-password.component.html | 18 +- .../login/create-password.component.scss | 2 +- .../pages/login/create-password.component.ts | 6 +- .../login/pages/login/login.component.html | 10 +- .../login/pages/login/login.component.scss | 13 +- .../login/pages/login/login.component.ts | 6 +- .../reset-password-request.component.html | 16 +- .../reset-password-request.component.scss | 2 +- .../login/reset-password-request.component.ts | 6 +- .../pages/login/reset-password.component.html | 24 +- .../pages/login/reset-password.component.scss | 2 +- .../pages/login/reset-password.component.ts | 6 +- .../two-factor-auth-login.component.html | 23 +- .../two-factor-auth-login.component.scss | 17 +- .../login/two-factor-auth-login.component.ts | 6 +- .../shared/adapter/custom-datatime-adapter.ts | 2 +- .../animations/speed-dial-fab.animations.ts | 2 +- .../components/breadcrumb.component.html | 35 +- .../components/breadcrumb.component.scss | 5 +- .../shared/components/breadcrumb.component.ts | 26 +- .../src/app/shared/components/breadcrumb.ts | 2 +- .../button/copy-button.component.html | 2 +- .../button/copy-button.component.scss | 6 +- .../button/copy-button.component.ts | 5 +- .../button/toggle-password.component.html | 2 +- .../button/toggle-password.component.ts | 2 +- .../shared/components/cheatsheet.component.ts | 2 +- .../components/circular-progress.directive.ts | 13 +- .../components/color-input.component.html | 8 +- .../components/color-input.component.scss | 17 +- .../components/color-input.component.ts | 8 +- .../shared/components/contact.component.html | 4 +- .../shared/components/contact.component.ts | 6 +- .../app/shared/components/css.component.html | 4 +- .../app/shared/components/css.component.scss | 7 +- .../app/shared/components/css.component.ts | 6 +- .../dashboard-autocomplete.component.html | 8 +- .../dashboard-autocomplete.component.ts | 18 +- .../dashboard-select-panel.component.html | 2 +- .../dashboard-select-panel.component.scss | 15 +- .../dashboard-select-panel.component.ts | 2 +- .../dashboard-select.component.html | 4 +- .../dashboard-select.component.scss | 12 +- .../components/dashboard-select.component.ts | 118 +- ...ashboard-state-autocomplete.component.html | 42 + .../dashboard-state-autocomplete.component.ts | 231 + .../app/shared/components/dialog.component.ts | 2 +- .../dialog/alert-dialog.component.html | 2 +- .../dialog/alert-dialog.component.scss | 4 +- .../dialog/alert-dialog.component.ts | 2 +- .../dialog/color-picker-dialog.component.html | 2 +- .../dialog/color-picker-dialog.component.ts | 10 +- .../dialog/confirm-dialog.component.html | 2 +- .../dialog/confirm-dialog.component.scss | 4 +- .../dialog/confirm-dialog.component.ts | 2 +- .../json-object-edit-dialog.component.html | 4 +- .../json-object-edit-dialog.component.ts | 8 +- .../material-icons-dialog.component.html | 7 +- .../material-icons-dialog.component.scss | 14 +- .../dialog/material-icons-dialog.component.ts | 8 +- .../node-script-test-dialog.component.html | 5 +- .../node-script-test-dialog.component.scss | 6 +- .../node-script-test-dialog.component.ts | 10 +- .../dialog/todo-dialog.component.html | 2 +- .../dialog/todo-dialog.component.scss | 4 +- .../dialog/todo-dialog.component.ts | 2 +- .../directives/component-outlet.directive.ts | 8 +- .../sring-template-outlet.directive.ts | 8 +- .../directives/tb-json-to-string.directive.ts | 8 +- .../entity/entity-autocomplete.component.html | 9 +- .../entity/entity-autocomplete.component.ts | 78 +- .../entity-gateway-select.component.html | 2 +- .../entity/entity-gateway-select.component.ts | 8 +- .../entity/entity-keys-list.component.html | 13 +- .../entity/entity-keys-list.component.ts | 14 +- .../entity/entity-list-select.component.html | 2 +- .../entity/entity-list-select.component.scss | 4 +- .../entity/entity-list-select.component.ts | 8 +- .../entity/entity-list.component.html | 25 +- .../entity/entity-list.component.ts | 30 +- .../entity/entity-select.component.html | 2 +- .../entity/entity-select.component.scss | 2 +- .../entity/entity-select.component.ts | 8 +- ...entity-subtype-autocomplete.component.html | 6 +- .../entity-subtype-autocomplete.component.ts | 22 +- .../entity/entity-subtype-list.component.html | 13 +- .../entity/entity-subtype-list.component.scss | 22 - .../entity/entity-subtype-list.component.ts | 14 +- .../entity-subtype-select.component.html | 4 +- .../entity-subtype-select.component.scss | 2 +- .../entity/entity-subtype-select.component.ts | 12 +- .../entity/entity-type-list.component.html | 13 +- .../entity/entity-type-list.component.ts | 18 +- .../entity/entity-type-select.component.html | 2 +- .../entity/entity-type-select.component.scss | 9 +- .../entity/entity-type-select.component.ts | 8 +- .../components/fab-toolbar.component.html | 2 +- .../components/fab-toolbar.component.scss | 49 +- .../components/fab-toolbar.component.ts | 18 +- .../components/file-input.component.html | 4 +- .../components/file-input.component.scss | 11 +- .../shared/components/file-input.component.ts | 2 +- .../footer-fab-buttons.component.html | 2 +- .../footer-fab-buttons.component.scss | 3 +- .../footer-fab-buttons.component.ts | 2 +- .../shared/components/footer.component.html | 2 +- .../shared/components/footer.component.scss | 2 +- .../app/shared/components/footer.component.ts | 2 +- .../shared/components/fullscreen.directive.ts | 2 +- .../components/help-markdown.component.html | 2 +- .../components/help-markdown.component.scss | 2 +- .../components/help-markdown.component.ts | 2 +- .../components/help-popup.component.html | 16 +- .../components/help-popup.component.scss | 37 +- .../shared/components/help-popup.component.ts | 16 +- .../app/shared/components/help.component.html | 4 +- .../app/shared/components/help.component.ts | 6 +- .../shared/components/hotkeys.directive.ts | 2 +- .../app/shared/components/html.component.html | 6 +- .../app/shared/components/html.component.scss | 7 +- .../app/shared/components/html.component.ts | 6 +- .../components/image-input.component.html | 6 +- .../components/image-input.component.scss | 12 +- .../components/image-input.component.ts | 2 +- .../shared/components/js-func.component.html | 6 +- .../shared/components/js-func.component.scss | 7 +- .../shared/components/js-func.component.ts | 6 +- .../components/json-content.component.html | 4 +- .../components/json-content.component.scss | 7 +- .../components/json-content.component.ts | 6 +- .../json-form/json-form-component.models.ts | 2 +- .../json-form/json-form.component.html | 2 +- .../json-form/json-form.component.scss | 2 +- .../json-form/json-form.component.ts | 6 +- .../json-form/react/json-form-ace-editor.tsx | 2 +- .../json-form/react/json-form-array.tsx | 2 +- .../react/json-form-base-component.tsx | 2 +- .../json-form/react/json-form-checkbox.tsx | 2 +- .../json-form/react/json-form-color.tsx | 2 +- .../json-form/react/json-form-css.tsx | 2 +- .../json-form/react/json-form-date.tsx | 2 +- .../json-form/react/json-form-fieldset.tsx | 2 +- .../json-form/react/json-form-help.tsx | 2 +- .../json-form/react/json-form-html.tsx | 2 +- .../json-form/react/json-form-icon.tsx | 2 +- .../json-form/react/json-form-image.tsx | 2 +- .../json-form/react/json-form-javascript.tsx | 2 +- .../json-form/react/json-form-json.tsx | 2 +- .../json-form/react/json-form-markdown.tsx | 2 +- .../json-form/react/json-form-number.tsx | 2 +- .../json-form/react/json-form-radios.tsx | 2 +- .../json-form/react/json-form-rc-select.tsx | 2 +- .../json-form/react/json-form-react.tsx | 2 +- .../json-form/react/json-form-schema-form.tsx | 2 +- .../json-form/react/json-form-select.tsx | 2 +- .../json-form/react/json-form-text.tsx | 2 +- .../json-form/react/json-form-utils.ts | 2 +- .../json-form/react/json-form.models.ts | 2 +- .../components/json-form/react/json-form.scss | 2 +- .../react/styles/thingsboardTheme.ts | 22 +- .../json-object-edit.component.html | 4 +- .../json-object-edit.component.scss | 7 +- .../components/json-object-edit.component.ts | 6 +- .../json-object-view.component.html | 2 +- .../json-object-view.component.scss | 2 +- .../components/json-object-view.component.ts | 2 +- .../shared/components/kv-map.component.html | 16 +- .../shared/components/kv-map.component.scss | 14 +- .../app/shared/components/kv-map.component.ts | 61 +- .../components/led-light.component.html | 2 +- .../shared/components/led-light.component.ts | 2 +- .../app/shared/components/logo.component.html | 2 +- .../app/shared/components/logo.component.scss | 2 +- .../app/shared/components/logo.component.ts | 2 +- .../components/markdown-editor.component.html | 8 +- .../components/markdown-editor.component.scss | 40 +- .../components/markdown-editor.component.ts | 2 +- .../shared/components/markdown.component.html | 2 +- .../shared/components/markdown.component.scss | 406 + .../shared/components/markdown.component.ts | 82 +- .../components/marked-options.service.ts | 2 +- .../mat-chip-draggable.directive.ts | 271 - .../material-icon-select.component.html | 4 +- .../material-icon-select.component.scss | 11 +- .../material-icon-select.component.ts | 8 +- .../message-type-autocomplete.component.html | 4 +- .../message-type-autocomplete.component.ts | 8 +- .../multiple-image-input.component.html | 4 +- .../multiple-image-input.component.scss | 12 +- .../multiple-image-input.component.ts | 2 +- .../shared/components/nav-tree.component.html | 2 +- .../shared/components/nav-tree.component.scss | 6 +- .../shared/components/nav-tree.component.ts | 2 +- .../notification/notification.component.html | 51 + .../notification/notification.component.scss | 71 + .../notification/notification.component.ts | 150 + .../template-autocomplete.component.html | 64 + .../template-autocomplete.component.scss | 30 + .../template-autocomplete.component.ts | 243 + .../ota-package-autocomplete.component.html | 7 +- .../ota-package-autocomplete.component.scss | 4 +- .../ota-package-autocomplete.component.ts | 8 +- .../app/shared/components/page.component.ts | 2 +- .../components/phone-input.component.html | 7 +- .../components/phone-input.component.scss | 9 +- .../components/phone-input.component.ts | 21 +- .../shared/components/popover.component.scss | 2 +- .../shared/components/popover.component.ts | 8 +- .../app/shared/components/popover.models.ts | 2 +- .../app/shared/components/popover.service.ts | 7 +- .../protobuf-content.component.html | 4 +- .../protobuf-content.component.scss | 7 +- .../components/protobuf-content.component.ts | 2 +- .../src/app/shared/components/public-api.ts | 4 +- .../queue/queue-autocomplete.component.html | 13 +- .../queue/queue-autocomplete.component.scss | 20 +- .../queue/queue-autocomplete.component.ts | 15 +- .../relation-type-autocomplete.component.html | 8 +- .../relation-type-autocomplete.component.ts | 12 +- .../components/script-lang.component.html | 2 +- .../components/script-lang.component.scss | 4 +- .../components/script-lang.component.ts | 8 +- ...k-conversation-autocomplete.component.html | 52 + ...-conversation-autocomplete.component.scss} | 13 +- ...ack-conversation-autocomplete.component.ts | 240 + .../components/snack-bar-component.html | 2 +- .../components/snack-bar-component.scss | 3 +- .../socialshare-panel.component.html | 10 +- .../socialshare-panel.component.scss | 2 +- .../components/socialshare-panel.component.ts | 2 +- .../string-items-list.component.html | 38 + .../components/string-items-list.component.ts | 159 + .../shared/components/tb-anchor.component.ts | 2 +- .../components/tb-checkbox.component.html | 2 +- .../components/tb-checkbox.component.ts | 2 +- .../shared/components/tb-error.component.ts | 2 +- .../time/datetime-period.component.html | 10 +- .../time/datetime-period.component.scss | 12 +- .../time/datetime-period.component.ts | 2 +- .../components/time/datetime.component.html | 10 +- .../components/time/datetime.component.ts | 4 +- .../history-selector.component.html | 4 +- .../history-selector.component.scss | 16 +- .../history-selector.component.ts | 2 +- .../time/quick-time-interval.component.html | 2 +- .../time/quick-time-interval.component.scss | 2 +- .../time/quick-time-interval.component.ts | 2 +- .../time/timeinterval.component.html | 15 +- .../time/timeinterval.component.scss | 14 +- .../components/time/timeinterval.component.ts | 41 +- .../time/timewindow-panel.component.html | 436 +- .../time/timewindow-panel.component.scss | 33 +- .../time/timewindow-panel.component.ts | 142 +- .../components/time/timewindow.component.html | 20 +- .../components/time/timewindow.component.scss | 2 +- .../components/time/timewindow.component.ts | 135 +- .../time/timezone-select.component.html | 4 +- .../time/timezone-select.component.ts | 12 +- .../app/shared/components/toast.directive.ts | 8 +- ui-ngx/src/app/shared/components/tokens.ts | 2 +- .../components/user-menu.component.html | 4 +- .../components/user-menu.component.scss | 2 +- .../shared/components/user-menu.component.ts | 2 +- .../components/value-input.component.html | 6 +- .../components/value-input.component.scss | 12 +- .../components/value-input.component.ts | 2 +- .../vc/branch-autocomplete.component.html | 9 +- .../vc/branch-autocomplete.component.scss | 6 +- .../vc/branch-autocomplete.component.ts | 18 +- .../widgets-bundle-search.component.html | 2 +- .../widgets-bundle-search.component.scss | 2 +- .../widgets-bundle-search.component.ts | 2 +- .../widgets-bundle-select.component.html | 5 +- .../widgets-bundle-select.component.scss | 97 +- .../widgets-bundle-select.component.ts | 2 +- .../app/shared/decorators/coerce-boolean.ts | 34 + .../src/app/shared/decorators/enumerable.ts | 2 +- ui-ngx/src/app/shared/decorators/tb-inject.ts | 2 +- .../src/app/shared/models/ace/ace.models.ts | 2 +- .../shared/models/ace/completion.models.ts | 2 +- .../models/ace/service-completion.models.ts | 2 +- .../models/ace/widget-completion.models.ts | 2 +- ui-ngx/src/app/shared/models/alarm.models.ts | 74 +- ui-ngx/src/app/shared/models/alias.models.ts | 4 +- .../src/app/shared/models/api-usage.models.ts | 47 + ui-ngx/src/app/shared/models/asset.models.ts | 4 +- .../src/app/shared/models/audit-log.models.ts | 12 +- .../src/app/shared/models/authority.enum.ts | 2 +- ui-ngx/src/app/shared/models/base-data.ts | 13 +- .../src/app/shared/models/beautify.models.ts | 2 +- .../models/component-descriptor.models.ts | 2 +- ui-ngx/src/app/shared/models/constants.ts | 6 +- .../app/shared/models/contact-based.model.ts | 2 +- .../src/app/shared/models/country.models.ts | 2 +- .../src/app/shared/models/customer.model.ts | 2 +- .../src/app/shared/models/dashboard.models.ts | 2 +- ui-ngx/src/app/shared/models/device.models.ts | 10 +- ui-ngx/src/app/shared/models/edge.models.ts | 6 +- .../app/shared/models/entity-type.models.ts | 74 +- .../app/shared/models/entity-view.models.ts | 2 +- ui-ngx/src/app/shared/models/entity.models.ts | 2 +- ui-ngx/src/app/shared/models/error.models.ts | 2 +- ui-ngx/src/app/shared/models/event.models.ts | 2 +- .../app/shared/models/id/alarm-comment-id.ts | 24 + ui-ngx/src/app/shared/models/id/alarm-id.ts | 2 +- ui-ngx/src/app/shared/models/id/asset-id.ts | 2 +- .../app/shared/models/id/asset-profile-id.ts | 2 +- .../src/app/shared/models/id/audit-log-id.ts | 2 +- .../src/app/shared/models/id/customer-id.ts | 2 +- .../src/app/shared/models/id/dashboard-id.ts | 2 +- .../shared/models/id/device-credentials-id.ts | 2 +- ui-ngx/src/app/shared/models/id/device-id.ts | 2 +- .../app/shared/models/id/device-profile-id.ts | 2 +- ui-ngx/src/app/shared/models/id/edge-id.ts | 2 +- ui-ngx/src/app/shared/models/id/entity-id.ts | 2 +- .../app/shared/models/id/entity-view-id.ts | 2 +- ui-ngx/src/app/shared/models/id/event-id.ts | 2 +- ui-ngx/src/app/shared/models/id/has-uuid.ts | 2 +- .../app/shared/models/id/notification-id.ts | 26 + .../models/id/notification-request-id.ts | 26 + .../shared/models/id/notification-rule-id.ts | 26 + .../models/id/notification-target-id.ts | 26 + .../models/id/notification-template-id.ts | 26 + .../app/shared/models/id/ota-package-id.ts | 2 +- ui-ngx/src/app/shared/models/id/public-api.ts | 7 +- ui-ngx/src/app/shared/models/id/queue-id.ts | 2 +- ui-ngx/src/app/shared/models/id/rpc-id.ts | 2 +- .../src/app/shared/models/id/rule-chain-id.ts | 2 +- .../src/app/shared/models/id/rule-node-id.ts | 2 +- .../app/shared/models/id/tb-resource-id.ts | 2 +- ui-ngx/src/app/shared/models/id/tenant-id.ts | 2 +- .../app/shared/models/id/tenant-profile-id.ts | 2 +- ui-ngx/src/app/shared/models/id/user-id.ts | 2 +- .../app/shared/models/id/widget-type-id.ts | 2 +- .../app/shared/models/id/widgets-bundle-id.ts | 2 +- ui-ngx/src/app/shared/models/login.models.ts | 2 +- .../models/lwm2m-security-config.models.ts | 2 +- .../src/app/shared/models/material.models.ts | 2 +- .../app/shared/models/notification.models.ts | 567 + ui-ngx/src/app/shared/models/oauth2.models.ts | 2 +- .../app/shared/models/ota-package.models.ts | 2 +- .../src/app/shared/models/page/page-data.ts | 2 +- .../src/app/shared/models/page/page-link.ts | 2 +- .../src/app/shared/models/page/public-api.ts | 2 +- .../src/app/shared/models/page/sort-order.ts | 2 +- ui-ngx/src/app/shared/models/public-api.ts | 7 +- .../app/shared/models/query/query.models.ts | 2 +- ui-ngx/src/app/shared/models/queue.models.ts | 2 +- .../src/app/shared/models/relation.models.ts | 2 +- .../src/app/shared/models/resource.models.ts | 2 +- ui-ngx/src/app/shared/models/rpc.models.ts | 2 +- .../app/shared/models/rule-chain.models.ts | 2 +- .../src/app/shared/models/rule-node.models.ts | 11 +- .../src/app/shared/models/settings.models.ts | 2 +- .../models/telemetry/telemetry.models.ts | 66 +- ui-ngx/src/app/shared/models/tenant.model.ts | 3 +- .../src/app/shared/models/time/time.models.ts | 96 +- .../shared/models/two-factor-auth.models.ts | 2 +- .../app/shared/models/user-settings.models.ts | 23 + ui-ngx/src/app/shared/models/user.model.ts | 10 +- ui-ngx/src/app/shared/models/vc.models.ts | 2 +- .../websocket/notification-ws.models.ts | 240 + .../models/websocket/websocket.models.ts | 67 + ui-ngx/src/app/shared/models/widget.models.ts | 10 +- .../app/shared/models/widgets-bundle.model.ts | 2 +- .../app/shared/models/window-message.model.ts | 2 +- ui-ngx/src/app/shared/pipe/date-ago.pipe.ts | 58 + .../src/app/shared/pipe/enum-to-array.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/file-size.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/highlight.pipe.ts | 2 +- .../app/shared/pipe/keyboard-shortcut.pipe.ts | 2 +- .../pipe/milliseconds-to-time-string.pipe.ts | 56 +- ui-ngx/src/app/shared/pipe/nospace.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/public-api.ts | 3 +- ui-ngx/src/app/shared/pipe/safe.pipe.ts | 2 +- .../shared/pipe/selectable-columns.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/tbJson.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/truncate.pipe.ts | 2 +- ui-ngx/src/app/shared/public-api.ts | 2 +- .../shared/services/custom-paginator-intl.ts | 2 +- ui-ngx/src/app/shared/shared.module.ts | 59 +- ui-ngx/src/assets/fonts/material-icons.css | 2 +- .../assets/help/en_US/notification/alarm.md | 63 + .../en_US/notification/alarm_assignment.md | 70 + .../help/en_US/notification/alarm_comment.md | 68 + .../en_US/notification/api_usage_limit.md | 48 + .../en_US/notification/device_activity.md | 64 + .../help/en_US/notification/entities_limit.md | 47 + .../help/en_US/notification/entity_action.md | 65 + .../assets/help/en_US/notification/general.md | 41 + .../help/en_US/notification/rule_engine.md | 54 + .../rule_engine_lifecycle_event.md | 49 + .../widget/action/custom_additional_params.md | 8 +- .../custom_pretty_create_user_js.md | 2 +- .../help/en_US/widget/lib/map/position_fn.md | 30 +- .../widget/lib/markdown/markdown_text_fn.md | 5 +- .../assets/locale/locale.constant-ca_ES.json | 317 +- .../assets/locale/locale.constant-cs_CZ.json | 215 +- .../assets/locale/locale.constant-da_DK.json | 17 +- .../assets/locale/locale.constant-de_DE.json | 162 +- .../assets/locale/locale.constant-el_GR.json | 220 +- .../assets/locale/locale.constant-en_US.json | 628 +- .../assets/locale/locale.constant-es_ES.json | 239 +- .../assets/locale/locale.constant-fa_IR.json | 142 +- .../assets/locale/locale.constant-fr_FR.json | 190 +- .../assets/locale/locale.constant-it_IT.json | 142 +- .../assets/locale/locale.constant-ja_JP.json | 130 +- .../assets/locale/locale.constant-ka_GE.json | 148 +- .../assets/locale/locale.constant-ko_KR.json | 181 +- .../assets/locale/locale.constant-lv_LV.json | 142 +- .../assets/locale/locale.constant-pt_BR.json | 160 +- .../assets/locale/locale.constant-ro_RO.json | 142 +- .../assets/locale/locale.constant-ru_RU.json | 136 +- .../assets/locale/locale.constant-sl_SI.json | 181 +- .../assets/locale/locale.constant-tr_TR.json | 215 +- .../assets/locale/locale.constant-uk_UA.json | 192 +- .../assets/locale/locale.constant-zh_CN.json | 265 +- .../assets/locale/locale.constant-zh_TW.json | 6193 ++++++++--- ui-ngx/src/assets/mdi.svg | 1 - ui-ngx/src/assets/notification-bell.svg | 1 + ui-ngx/src/environments/environment.prod.ts | 2 +- ui-ngx/src/environments/environment.ts | 2 +- ui-ngx/src/index.html | 2 +- ui-ngx/src/karma.conf.js | 2 +- ui-ngx/src/main.ts | 2 +- ui-ngx/src/polyfills.ts | 12 +- ui-ngx/src/scss/animations.scss | 2 +- ui-ngx/src/scss/constants.scss | 2 +- ui-ngx/src/scss/fonts.scss | 2 +- ui-ngx/src/scss/mixins.scss | 6 +- ui-ngx/src/styles.scss | 811 +- ui-ngx/src/test.ts | 12 +- ui-ngx/src/theme-overwrites.scss | 54 + ui-ngx/src/theme.scss | 78 +- ui-ngx/src/theme/datepicker-theme.scss | 70 + ui-ngx/src/tsconfig.app.json | 2 +- ui-ngx/src/typings/jquery.flot.typings.d.ts | 2 +- ui-ngx/src/typings/jquery.jstree.typings.d.ts | 687 +- ui-ngx/src/typings/jquery.typings.d.ts | 2 +- ui-ngx/src/typings/leaflet-extend-tb.d.ts | 2 +- ui-ngx/src/typings/leaflet-geoman-extend.d.ts | 4 +- ui-ngx/src/typings/rawloader.typings.d.ts | 2 +- ui-ngx/src/typings/split.js.typings.d.ts | 2 +- ui-ngx/src/zone-flags.ts | 2 +- ui-ngx/tsconfig.json | 33 +- ui-ngx/tslint.json | 139 - ui-ngx/yarn.lock | 9592 +++++++++-------- 5586 files changed, 130425 insertions(+), 30706 deletions(-) create mode 100644 application/src/main/data/json/edge/install_instructions/docker/instructions.md create mode 100644 application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md rename application/src/main/data/json/{tenant/edge_management => edge}/rule_chains/edge_root_rule_chain.json (83%) create mode 100644 application/src/main/data/lwm2m-registry/0.xml create mode 100644 application/src/main/data/lwm2m-registry/1.xml create mode 100644 application/src/main/data/lwm2m-registry/10.xml create mode 100644 application/src/main/data/lwm2m-registry/10241.xml create mode 100644 application/src/main/data/lwm2m-registry/10242.xml create mode 100644 application/src/main/data/lwm2m-registry/10243.xml create mode 100644 application/src/main/data/lwm2m-registry/10244.xml create mode 100644 application/src/main/data/lwm2m-registry/10245.xml create mode 100644 application/src/main/data/lwm2m-registry/10246.xml create mode 100644 application/src/main/data/lwm2m-registry/10247.xml create mode 100644 application/src/main/data/lwm2m-registry/10248.xml create mode 100644 application/src/main/data/lwm2m-registry/10249.xml create mode 100644 application/src/main/data/lwm2m-registry/10250.xml create mode 100644 application/src/main/data/lwm2m-registry/10251.xml create mode 100644 application/src/main/data/lwm2m-registry/10252.xml create mode 100644 application/src/main/data/lwm2m-registry/10253.xml create mode 100644 application/src/main/data/lwm2m-registry/10254.xml create mode 100644 application/src/main/data/lwm2m-registry/10255.xml create mode 100644 application/src/main/data/lwm2m-registry/10256.xml create mode 100644 application/src/main/data/lwm2m-registry/10257.xml create mode 100644 application/src/main/data/lwm2m-registry/10258.xml create mode 100644 application/src/main/data/lwm2m-registry/10259.xml create mode 100644 application/src/main/data/lwm2m-registry/10260.xml create mode 100644 application/src/main/data/lwm2m-registry/10262.xml create mode 100644 application/src/main/data/lwm2m-registry/10263.xml create mode 100644 application/src/main/data/lwm2m-registry/10264.xml create mode 100644 application/src/main/data/lwm2m-registry/10265.xml create mode 100644 application/src/main/data/lwm2m-registry/10266.xml create mode 100644 application/src/main/data/lwm2m-registry/10267.xml create mode 100644 application/src/main/data/lwm2m-registry/10268.xml create mode 100644 application/src/main/data/lwm2m-registry/10269.xml create mode 100644 application/src/main/data/lwm2m-registry/10270.xml create mode 100644 application/src/main/data/lwm2m-registry/10271.xml create mode 100644 application/src/main/data/lwm2m-registry/10272.xml create mode 100644 application/src/main/data/lwm2m-registry/10273.xml create mode 100644 application/src/main/data/lwm2m-registry/10274.xml create mode 100644 application/src/main/data/lwm2m-registry/10275.xml create mode 100644 application/src/main/data/lwm2m-registry/10276.xml create mode 100644 application/src/main/data/lwm2m-registry/10277.xml create mode 100644 application/src/main/data/lwm2m-registry/10278.xml create mode 100644 application/src/main/data/lwm2m-registry/10279.xml create mode 100644 application/src/main/data/lwm2m-registry/10280.xml create mode 100644 application/src/main/data/lwm2m-registry/10281.xml create mode 100644 application/src/main/data/lwm2m-registry/10282.xml create mode 100644 application/src/main/data/lwm2m-registry/10283.xml create mode 100644 application/src/main/data/lwm2m-registry/10284.xml create mode 100644 application/src/main/data/lwm2m-registry/10286.xml create mode 100644 application/src/main/data/lwm2m-registry/10290.xml create mode 100644 application/src/main/data/lwm2m-registry/10291.xml create mode 100644 application/src/main/data/lwm2m-registry/10292.xml create mode 100644 application/src/main/data/lwm2m-registry/10299.xml create mode 100644 application/src/main/data/lwm2m-registry/10300.xml create mode 100644 application/src/main/data/lwm2m-registry/10308.xml create mode 100644 application/src/main/data/lwm2m-registry/10309.xml create mode 100644 application/src/main/data/lwm2m-registry/10311.xml create mode 100644 application/src/main/data/lwm2m-registry/10313.xml create mode 100644 application/src/main/data/lwm2m-registry/10314.xml create mode 100644 application/src/main/data/lwm2m-registry/10315.xml create mode 100644 application/src/main/data/lwm2m-registry/10316.xml create mode 100644 application/src/main/data/lwm2m-registry/10318.xml create mode 100644 application/src/main/data/lwm2m-registry/10319.xml create mode 100644 application/src/main/data/lwm2m-registry/10320.xml create mode 100644 application/src/main/data/lwm2m-registry/10322.xml create mode 100644 application/src/main/data/lwm2m-registry/10323.xml create mode 100644 application/src/main/data/lwm2m-registry/10324.xml create mode 100644 application/src/main/data/lwm2m-registry/10326.xml create mode 100644 application/src/main/data/lwm2m-registry/10327.xml create mode 100644 application/src/main/data/lwm2m-registry/10328.xml create mode 100644 application/src/main/data/lwm2m-registry/10329.xml create mode 100644 application/src/main/data/lwm2m-registry/10330.xml create mode 100644 application/src/main/data/lwm2m-registry/10331.xml create mode 100644 application/src/main/data/lwm2m-registry/10332.xml create mode 100644 application/src/main/data/lwm2m-registry/10333.xml create mode 100644 application/src/main/data/lwm2m-registry/10334.xml create mode 100644 application/src/main/data/lwm2m-registry/10335.xml create mode 100644 application/src/main/data/lwm2m-registry/10336.xml create mode 100644 application/src/main/data/lwm2m-registry/10337.xml create mode 100644 application/src/main/data/lwm2m-registry/10338.xml create mode 100644 application/src/main/data/lwm2m-registry/10339.xml create mode 100644 application/src/main/data/lwm2m-registry/10340.xml create mode 100644 application/src/main/data/lwm2m-registry/10341.xml create mode 100644 application/src/main/data/lwm2m-registry/10342.xml create mode 100644 application/src/main/data/lwm2m-registry/10343.xml create mode 100644 application/src/main/data/lwm2m-registry/10344.xml create mode 100644 application/src/main/data/lwm2m-registry/10345.xml create mode 100644 application/src/main/data/lwm2m-registry/10346.xml create mode 100644 application/src/main/data/lwm2m-registry/10347.xml create mode 100644 application/src/main/data/lwm2m-registry/10348.xml create mode 100644 application/src/main/data/lwm2m-registry/10349.xml create mode 100644 application/src/main/data/lwm2m-registry/10350.xml create mode 100644 application/src/main/data/lwm2m-registry/10351.xml create mode 100644 application/src/main/data/lwm2m-registry/10352.xml create mode 100644 application/src/main/data/lwm2m-registry/10353.xml create mode 100644 application/src/main/data/lwm2m-registry/10354.xml create mode 100644 application/src/main/data/lwm2m-registry/10355.xml create mode 100644 application/src/main/data/lwm2m-registry/10356.xml create mode 100644 application/src/main/data/lwm2m-registry/10357.xml create mode 100644 application/src/main/data/lwm2m-registry/10358.xml create mode 100644 application/src/main/data/lwm2m-registry/10359.xml create mode 100644 application/src/main/data/lwm2m-registry/10360.xml create mode 100644 application/src/main/data/lwm2m-registry/10361.xml create mode 100644 application/src/main/data/lwm2m-registry/10362.xml create mode 100644 application/src/main/data/lwm2m-registry/10363.xml create mode 100644 application/src/main/data/lwm2m-registry/10364.xml create mode 100644 application/src/main/data/lwm2m-registry/10365.xml create mode 100644 application/src/main/data/lwm2m-registry/10366.xml create mode 100644 application/src/main/data/lwm2m-registry/10368.xml create mode 100644 application/src/main/data/lwm2m-registry/10369.xml create mode 100644 application/src/main/data/lwm2m-registry/10371.xml create mode 100644 application/src/main/data/lwm2m-registry/10374.xml create mode 100644 application/src/main/data/lwm2m-registry/10375.xml create mode 100644 application/src/main/data/lwm2m-registry/10376.xml create mode 100644 application/src/main/data/lwm2m-registry/10377.xml create mode 100644 application/src/main/data/lwm2m-registry/10378.xml create mode 100644 application/src/main/data/lwm2m-registry/11.xml create mode 100644 application/src/main/data/lwm2m-registry/12.xml create mode 100644 application/src/main/data/lwm2m-registry/13.xml create mode 100644 application/src/main/data/lwm2m-registry/14.xml create mode 100644 application/src/main/data/lwm2m-registry/15.xml create mode 100644 application/src/main/data/lwm2m-registry/16.xml create mode 100644 application/src/main/data/lwm2m-registry/18830.xml create mode 100644 application/src/main/data/lwm2m-registry/18831.xml create mode 100644 application/src/main/data/lwm2m-registry/19.xml create mode 100644 application/src/main/data/lwm2m-registry/2.xml create mode 100644 application/src/main/data/lwm2m-registry/20.xml create mode 100644 application/src/main/data/lwm2m-registry/2048.xml create mode 100644 application/src/main/data/lwm2m-registry/2049.xml create mode 100644 application/src/main/data/lwm2m-registry/2050.xml create mode 100644 application/src/main/data/lwm2m-registry/2051.xml create mode 100644 application/src/main/data/lwm2m-registry/2052.xml create mode 100644 application/src/main/data/lwm2m-registry/2053.xml create mode 100644 application/src/main/data/lwm2m-registry/2054.xml create mode 100644 application/src/main/data/lwm2m-registry/2055.xml create mode 100644 application/src/main/data/lwm2m-registry/2056.xml create mode 100644 application/src/main/data/lwm2m-registry/2057.xml create mode 100644 application/src/main/data/lwm2m-registry/21.xml create mode 100644 application/src/main/data/lwm2m-registry/22.xml create mode 100644 application/src/main/data/lwm2m-registry/23.xml create mode 100644 application/src/main/data/lwm2m-registry/24.xml create mode 100644 application/src/main/data/lwm2m-registry/25.xml create mode 100644 application/src/main/data/lwm2m-registry/26.xml create mode 100644 application/src/main/data/lwm2m-registry/27.xml create mode 100644 application/src/main/data/lwm2m-registry/28.xml create mode 100644 application/src/main/data/lwm2m-registry/3.xml create mode 100644 application/src/main/data/lwm2m-registry/3200.xml create mode 100644 application/src/main/data/lwm2m-registry/3201.xml create mode 100644 application/src/main/data/lwm2m-registry/3202.xml create mode 100644 application/src/main/data/lwm2m-registry/3203.xml create mode 100644 application/src/main/data/lwm2m-registry/3300.xml create mode 100644 application/src/main/data/lwm2m-registry/3301.xml create mode 100644 application/src/main/data/lwm2m-registry/3302.xml create mode 100644 application/src/main/data/lwm2m-registry/3303.xml create mode 100644 application/src/main/data/lwm2m-registry/3304.xml create mode 100644 application/src/main/data/lwm2m-registry/3305.xml create mode 100644 application/src/main/data/lwm2m-registry/3306.xml create mode 100644 application/src/main/data/lwm2m-registry/3308.xml create mode 100644 application/src/main/data/lwm2m-registry/3310.xml create mode 100644 application/src/main/data/lwm2m-registry/3311.xml create mode 100644 application/src/main/data/lwm2m-registry/3312.xml create mode 100644 application/src/main/data/lwm2m-registry/3313.xml create mode 100644 application/src/main/data/lwm2m-registry/3314.xml create mode 100644 application/src/main/data/lwm2m-registry/3315.xml create mode 100644 application/src/main/data/lwm2m-registry/3316.xml create mode 100644 application/src/main/data/lwm2m-registry/3317.xml create mode 100644 application/src/main/data/lwm2m-registry/3318.xml create mode 100644 application/src/main/data/lwm2m-registry/3319.xml create mode 100644 application/src/main/data/lwm2m-registry/3320.xml create mode 100644 application/src/main/data/lwm2m-registry/3321.xml create mode 100644 application/src/main/data/lwm2m-registry/3322.xml create mode 100644 application/src/main/data/lwm2m-registry/3323.xml create mode 100644 application/src/main/data/lwm2m-registry/3324.xml create mode 100644 application/src/main/data/lwm2m-registry/3325.xml create mode 100644 application/src/main/data/lwm2m-registry/3326.xml create mode 100644 application/src/main/data/lwm2m-registry/3327.xml create mode 100644 application/src/main/data/lwm2m-registry/3328.xml create mode 100644 application/src/main/data/lwm2m-registry/3329.xml create mode 100644 application/src/main/data/lwm2m-registry/3330.xml create mode 100644 application/src/main/data/lwm2m-registry/3331.xml create mode 100644 application/src/main/data/lwm2m-registry/3332.xml create mode 100644 application/src/main/data/lwm2m-registry/3333.xml create mode 100644 application/src/main/data/lwm2m-registry/3334.xml create mode 100644 application/src/main/data/lwm2m-registry/3335.xml create mode 100644 application/src/main/data/lwm2m-registry/3336.xml create mode 100644 application/src/main/data/lwm2m-registry/3337.xml create mode 100644 application/src/main/data/lwm2m-registry/3338.xml create mode 100644 application/src/main/data/lwm2m-registry/3339.xml create mode 100644 application/src/main/data/lwm2m-registry/3340.xml create mode 100644 application/src/main/data/lwm2m-registry/3341.xml create mode 100644 application/src/main/data/lwm2m-registry/3342.xml create mode 100644 application/src/main/data/lwm2m-registry/3343.xml create mode 100644 application/src/main/data/lwm2m-registry/3344.xml create mode 100644 application/src/main/data/lwm2m-registry/3345.xml create mode 100644 application/src/main/data/lwm2m-registry/3346.xml create mode 100644 application/src/main/data/lwm2m-registry/3347.xml create mode 100644 application/src/main/data/lwm2m-registry/3348.xml create mode 100644 application/src/main/data/lwm2m-registry/3349.xml create mode 100644 application/src/main/data/lwm2m-registry/3350.xml create mode 100644 application/src/main/data/lwm2m-registry/3351.xml create mode 100644 application/src/main/data/lwm2m-registry/3352.xml create mode 100644 application/src/main/data/lwm2m-registry/3353.xml create mode 100644 application/src/main/data/lwm2m-registry/3354.xml create mode 100644 application/src/main/data/lwm2m-registry/3355.xml create mode 100644 application/src/main/data/lwm2m-registry/3356.xml create mode 100644 application/src/main/data/lwm2m-registry/3357.xml create mode 100644 application/src/main/data/lwm2m-registry/3358.xml create mode 100644 application/src/main/data/lwm2m-registry/3359.xml create mode 100644 application/src/main/data/lwm2m-registry/3360.xml create mode 100644 application/src/main/data/lwm2m-registry/3361.xml create mode 100644 application/src/main/data/lwm2m-registry/3362.xml create mode 100644 application/src/main/data/lwm2m-registry/3363.xml create mode 100644 application/src/main/data/lwm2m-registry/3364.xml create mode 100644 application/src/main/data/lwm2m-registry/3365.xml create mode 100644 application/src/main/data/lwm2m-registry/3366.xml create mode 100644 application/src/main/data/lwm2m-registry/3367.xml create mode 100644 application/src/main/data/lwm2m-registry/3368.xml create mode 100644 application/src/main/data/lwm2m-registry/3369.xml create mode 100644 application/src/main/data/lwm2m-registry/3370.xml create mode 100644 application/src/main/data/lwm2m-registry/3371.xml create mode 100644 application/src/main/data/lwm2m-registry/3372.xml create mode 100644 application/src/main/data/lwm2m-registry/3373.xml create mode 100644 application/src/main/data/lwm2m-registry/3374.xml create mode 100644 application/src/main/data/lwm2m-registry/3375.xml create mode 100644 application/src/main/data/lwm2m-registry/3376.xml create mode 100644 application/src/main/data/lwm2m-registry/3377.xml create mode 100644 application/src/main/data/lwm2m-registry/3378.xml create mode 100644 application/src/main/data/lwm2m-registry/3379.xml create mode 100644 application/src/main/data/lwm2m-registry/3380.xml create mode 100644 application/src/main/data/lwm2m-registry/3381.xml create mode 100644 application/src/main/data/lwm2m-registry/3382.xml create mode 100644 application/src/main/data/lwm2m-registry/3383.xml create mode 100644 application/src/main/data/lwm2m-registry/3384.xml create mode 100644 application/src/main/data/lwm2m-registry/3385.xml create mode 100644 application/src/main/data/lwm2m-registry/3386.xml create mode 100644 application/src/main/data/lwm2m-registry/3387.xml create mode 100644 application/src/main/data/lwm2m-registry/3388.xml create mode 100644 application/src/main/data/lwm2m-registry/3389.xml create mode 100644 application/src/main/data/lwm2m-registry/3390.xml create mode 100644 application/src/main/data/lwm2m-registry/3391.xml create mode 100644 application/src/main/data/lwm2m-registry/3392.xml create mode 100644 application/src/main/data/lwm2m-registry/3393.xml create mode 100644 application/src/main/data/lwm2m-registry/3394.xml create mode 100644 application/src/main/data/lwm2m-registry/3395.xml create mode 100644 application/src/main/data/lwm2m-registry/3396.xml create mode 100644 application/src/main/data/lwm2m-registry/3397.xml create mode 100644 application/src/main/data/lwm2m-registry/3398.xml create mode 100644 application/src/main/data/lwm2m-registry/3399.xml create mode 100644 application/src/main/data/lwm2m-registry/3400.xml create mode 100644 application/src/main/data/lwm2m-registry/3401.xml create mode 100644 application/src/main/data/lwm2m-registry/3402.xml create mode 100644 application/src/main/data/lwm2m-registry/3403.xml create mode 100644 application/src/main/data/lwm2m-registry/3404.xml create mode 100644 application/src/main/data/lwm2m-registry/3405.xml create mode 100644 application/src/main/data/lwm2m-registry/3406.xml create mode 100644 application/src/main/data/lwm2m-registry/3407.xml create mode 100644 application/src/main/data/lwm2m-registry/3408.xml create mode 100644 application/src/main/data/lwm2m-registry/3410.xml create mode 100644 application/src/main/data/lwm2m-registry/3411.xml create mode 100644 application/src/main/data/lwm2m-registry/3412.xml create mode 100644 application/src/main/data/lwm2m-registry/3413.xml create mode 100644 application/src/main/data/lwm2m-registry/3414.xml create mode 100644 application/src/main/data/lwm2m-registry/3415.xml create mode 100644 application/src/main/data/lwm2m-registry/3416.xml create mode 100644 application/src/main/data/lwm2m-registry/3417.xml create mode 100644 application/src/main/data/lwm2m-registry/3418.xml create mode 100644 application/src/main/data/lwm2m-registry/3419.xml create mode 100644 application/src/main/data/lwm2m-registry/3420.xml create mode 100644 application/src/main/data/lwm2m-registry/3421.xml create mode 100644 application/src/main/data/lwm2m-registry/3423.xml create mode 100644 application/src/main/data/lwm2m-registry/3424.xml create mode 100644 application/src/main/data/lwm2m-registry/3425.xml create mode 100644 application/src/main/data/lwm2m-registry/3426.xml create mode 100644 application/src/main/data/lwm2m-registry/3427.xml create mode 100644 application/src/main/data/lwm2m-registry/3428.xml create mode 100644 application/src/main/data/lwm2m-registry/3429.xml create mode 100644 application/src/main/data/lwm2m-registry/3430.xml create mode 100644 application/src/main/data/lwm2m-registry/3431.xml create mode 100644 application/src/main/data/lwm2m-registry/3432.xml create mode 100644 application/src/main/data/lwm2m-registry/3433.xml create mode 100644 application/src/main/data/lwm2m-registry/3434.xml create mode 100644 application/src/main/data/lwm2m-registry/3435.xml create mode 100644 application/src/main/data/lwm2m-registry/3436.xml create mode 100644 application/src/main/data/lwm2m-registry/3437.xml create mode 100644 application/src/main/data/lwm2m-registry/3438.xml create mode 100644 application/src/main/data/lwm2m-registry/3439.xml create mode 100644 application/src/main/data/lwm2m-registry/3441.xml create mode 100644 application/src/main/data/lwm2m-registry/3442.xml create mode 100644 application/src/main/data/lwm2m-registry/4.xml create mode 100644 application/src/main/data/lwm2m-registry/5.xml create mode 100644 application/src/main/data/lwm2m-registry/500.xml create mode 100644 application/src/main/data/lwm2m-registry/501.xml create mode 100644 application/src/main/data/lwm2m-registry/502.xml create mode 100644 application/src/main/data/lwm2m-registry/503.xml create mode 100644 application/src/main/data/lwm2m-registry/504.xml create mode 100644 application/src/main/data/lwm2m-registry/505.xml create mode 100644 application/src/main/data/lwm2m-registry/507.xml create mode 100644 application/src/main/data/lwm2m-registry/508.xml create mode 100644 application/src/main/data/lwm2m-registry/509.xml create mode 100644 application/src/main/data/lwm2m-registry/510.xml create mode 100644 application/src/main/data/lwm2m-registry/6.xml create mode 100644 application/src/main/data/lwm2m-registry/7.xml create mode 100644 application/src/main/data/lwm2m-registry/8.xml create mode 100644 application/src/main/data/lwm2m-registry/9.xml create mode 100644 application/src/main/data/upgrade/3.4.4/schema_update.sql create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/UsageInfoController.java rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/AccessDeniedException.java (89%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/EntityNotFoundException.java (90%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/InternalErrorException.java (90%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/InvalidParametersException.java (90%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/ToErrorResponseEntity.java (87%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/UnauthorizedException.java (90%) rename application/src/main/java/org/thingsboard/server/{service/telemetry => }/exception/UncheckedApiException.java (90%) rename application/src/main/java/org/thingsboard/server/service/apiusage/{ => limits}/DefaultRateLimitService.java (64%) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java rename application/src/main/java/org/thingsboard/server/service/apiusage/{ => limits}/RateLimitService.java (75%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => asset}/AssetEdgeProcessor.java (95%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => asset}/AssetProfileEdgeProcessor.java (94%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => customer}/CustomerEdgeProcessor.java (96%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => dashboard}/DashboardEdgeProcessor.java (94%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => device}/DeviceEdgeProcessor.java (50%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => device}/DeviceProfileEdgeProcessor.java (94%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => edge}/EdgeProcessor.java (96%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => entityview}/EntityViewEdgeProcessor.java (94%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => ota}/OtaPackageEdgeProcessor.java (94%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => queue}/QueueEdgeProcessor.java (93%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => rule}/RuleChainEdgeProcessor.java (96%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => settings}/AdminSettingsEdgeProcessor.java (89%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{TelemetryEdgeProcessor.java => telemetry/BaseTelemetryProcessor.java} (68%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => user}/UserEdgeProcessor.java (94%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => widget}/WidgetBundleEdgeProcessor.java (94%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/{ => widget}/WidgetTypeEdgeProcessor.java (94%) create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java rename application/src/main/java/org/thingsboard/server/service/{telemetry/sub => subscription}/SubscriptionErrorCode.java (93%) create mode 100644 application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java create mode 100644 application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/system/SystemInfoService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java rename application/src/main/java/org/thingsboard/server/service/{telemetry/DefaultTelemetryWebSocketService.java => ws/DefaultWebSocketService.java} (72%) rename application/src/main/java/org/thingsboard/server/service/{telemetry => ws}/SessionEvent.java (93%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/TelemetryWebSocketMsgEndpoint.java => ws/WebSocketMsgEndpoint.java} (59%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/TelemetryWebSocketService.java => ws/WebSocketService.java} (60%) rename application/src/main/java/org/thingsboard/server/service/{telemetry/TelemetryWebSocketSessionRef.java => ws/WebSocketSessionRef.java} (67%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java rename application/src/main/java/org/thingsboard/server/service/{telemetry => ws}/WsSessionMetaData.java (77%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/TelemetryFeature.java (88%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/TelemetryWebSocketTextMsg.java (76%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/TelemetryPluginCmdsWrapper.java (52%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/AttributesSubscriptionCmd.java (82%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/GetHistoryCmd.java (90%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/SubscriptionCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/TelemetryPluginCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java (85%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AggHistoryCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AggKey.java (88%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AggTimeSeriesCmd.java (87%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUpdate.java rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AlarmDataCmd.java (90%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/AlarmDataUpdate.java (92%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/CmdUpdate.java (89%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/CmdUpdateType.java (76%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/DataCmd.java (84%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/DataUpdate.java (87%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityCountCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityCountUpdate.java (83%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityDataCmd.java (95%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java (86%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityDataUpdate.java (91%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/EntityHistoryCmd.java (89%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/GetTsCmd.java (88%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/LatestValueCmd.java (87%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/TimeSeriesCmd.java (90%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/cmd/v2/UnsubscribeCmd.java (85%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/sub/AlarmSubscriptionUpdate.java (68%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/sub/SubscriptionState.java (93%) rename application/src/main/java/org/thingsboard/server/service/{ => ws}/telemetry/sub/TelemetrySubscriptionUpdate.java (94%) delete mode 100644 application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java create mode 100644 application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/sql/AlarmCommentControllerSqlTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/sql/HomePageApiSqlTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/MqttTestCallback.java (96%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/MqttTestClient.java (94%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/AbstractMqttAttributesIntegrationTest.java (89%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/request/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java (96%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/request/MqttAttributesRequestIntegrationTest.java (91%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/request/MqttAttributesRequestJsonIntegrationTest.java (92%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/request/MqttAttributesRequestProtoIntegrationTest.java (96%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/updates/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java (95%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/updates/MqttAttributesUpdatesIntegrationTest.java (60%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/updates/MqttAttributesUpdatesJsonIntegrationTest.java (91%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/attributes/updates/MqttAttributesUpdatesProtoIntegrationTest.java (92%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/claim/MqttClaimBackwardCompatibilityDeviceTest.java (94%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/claim/MqttClaimDeviceTest.java (98%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/claim/MqttClaimJsonDeviceTest.java (94%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/claim/MqttClaimProtoDeviceTest.java (94%) create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/client/AbstractMqttClientConnectionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/client/MqttClientConnectionTest.java rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/credentials/BasicMqttCredentialsTest.java (90%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/provision/MqttProvisionJsonDeviceTest.java (97%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/provision/MqttProvisionProtoDeviceTest.java (97%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/rpc/AbstractMqttServerSideRpcIntegrationTest.java (93%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/rpc/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java (98%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/rpc/MqttServerSideRpcDefaultIntegrationTest.java (97%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/rpc/MqttServerSideRpcJsonIntegrationTest.java (95%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/rpc/MqttServerSideRpcProtoIntegrationTest.java (96%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/attributes/MqttAttributesIntegrationTest.java (98%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/attributes/MqttAttributesJsonIntegrationTest.java (94%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/attributes/MqttAttributesProtoIntegrationTest.java (99%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java (97%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java (93%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java (97%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java (75%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java (75%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java (75%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java (75%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java (75%) rename application/src/test/java/org/thingsboard/server/transport/mqtt/{ => mqttv3}/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java (75%) create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/AbstractMqttV5Test.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/MqttV5TestCallback.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/MqttV5TestClient.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/attributes/AbstractAttributesMqttV5Test.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/attributes/updates/AttributesUpdatesTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/attributes/upload/AttributesPublishTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/claim/AbstractMqttV5ClaimTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/claim/MqttV5ClaimTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/connection/AbstractMqttV5ClientConnectionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/connection/MqttV5ClientConnectionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/publish/AbstractMqttV5ClientPublishTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/publish/MqttV5ClientPublishTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/subscribe/AbstractMqttV5ClientSubscriptionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/subscribe/MqttV5ClientSubscriptionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/unsubscribe/AbstractMqttV5ClientUnsubscribeTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/unsubscribe/MqttV5ClientUnsubscribeTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/provision/MqttV5ProvisionDeviceTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/AbstractMqttV5RpcTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/MqttV5RpcTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/timeseries/AbstractMqttV5TimeseriesTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/timeseries/MqttV5TimeseriesTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/AbstractMqttV5ClientSparkplugTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/AbstractMqttV5ClientSparkplugAttributesTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesInProfileTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/connection/AbstractMqttV5ClientSparkplugConnectionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/connection/MqttV5ClientSparkplugBConnectionTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/rpc/AbstractMqttV5RpcSparkplugTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/rpc/MqttV5RpcSparkplugTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/timeseries/AbstractMqttV5ClientSparkplugTelemetryTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/timeseries/MqttV5ClientSparkplugBTelemetryTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/util/EventDeduplicationExecutorTest.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityCountService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityServiceRegistry.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/usage/UsageInfoService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiLimitService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordState.java rename common/data/src/main/java/org/thingsboard/server/common/data/{ApiUsageStateMailMessage.java => FeaturesInfo.java} (74%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/UsageInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmApiCallResult.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/AlreadySentException.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java rename ui-ngx/src/app/shared/components/entity/entity-keys-list.component.scss => common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java (75%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ApiUsageLimitNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceActivityNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleOriginatedNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ApiUsageLimitNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/AccountNotificationSettings.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationRecipient.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/AffectedTenantAdministratorsFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/AffectedUserFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/AllUsersFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/OriginatorEntityOwnerUsersFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/SystemAdministratorsFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/TenantAdministratorsFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilterType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversationType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/HasSubject.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmCountQuery.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java rename common/{util/src/main/java/org/thingsboard/common => data/src/main/java/org/thingsboard/server/common/data}/util/CollectionsUtil.java (68%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingBiFunction.java rename common/data/src/main/java/org/thingsboard/server/common/data/{sync => util}/ThrowingRunnable.java (89%) create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/NotificationRuleProcessor.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/AlarmTrigger.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/ApiUsageLimitTrigger.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NotificationRuleTrigger.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/RuleEngineComponentLifecycleEventTrigger.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/RuleEngineMsgTrigger.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java rename common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/{GatewayDeviceSessionCtx.java => AbstractGatewayDeviceSessionContext.java} (92%) create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionContext.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugDeviceSessionContext.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/ReturnCode.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/ReturnCodeResolver.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/MetricDataType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugConnectionState.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugMessageType.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugMetricUtil.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugRpcRequestHeader.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugRpcResponseBody.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugTopic.java create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/SparkplugTopicUtil.java create mode 100644 common/transport/mqtt/src/main/proto/sparkplug.proto create mode 100644 common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityCountService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/EntityCountCacheEvictEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/EntityCountCacheKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usage/BasicUsageInfoService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/DefaultApiLimitService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java delete mode 100644 dao/src/test/java/org/apache/cassandra/io/sstable/Descriptor.java delete mode 100644 dao/src/test/java/org/apache/cassandra/io/sstable/format/SSTableFormat.java delete mode 100644 dao/src/test/java/org/apache/cassandra/io/util/FileUtils.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/AbstractNoSqlContainer.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceRegistryTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/AlarmCommentServiceSqlTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceRegistrySqlTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/ProfilesPageElements.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/ProfilesPageHelper.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewHelper.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/AssetProfileEditMenuTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileImportTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteAssetProfileTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteSeveralAssetProfilesTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/MakeAssetProfileDefaultTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/SearchAssetProfileTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/SortByNameTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/CreateDeviceProfileImportTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/CreateDeviceProfileTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/DeleteDeviceProfileTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/DeleteSeveralDeviceProfilesTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/DeviceProfileEditMenuTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/MakeDeviceProfileDefaultTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/SearchDeviceProfileTest.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/SortByNameTest.java create mode 100644 msa/black-box-tests/src/test/resources/assetProfileForImport.json create mode 100644 msa/black-box-tests/src/test/resources/config.properties create mode 100644 msa/black-box-tests/src/test/resources/deviceProfileForImport.json create mode 100644 msa/black-box-tests/src/test/resources/docker-compose.hybrid-test-extras.yml create mode 100644 msa/black-box-tests/src/test/resources/docker-compose.postgres-test-extras.yml rename msa/black-box-tests/src/test/resources/{forImport.json => ruleChainForImport.json} (90%) create mode 100644 msa/black-box-tests/src/test/resources/smokesProfiles.xml create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/DeduplicationData.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/DeduplicationId.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/DeduplicationStrategy.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAbstractTypeSwitchNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbMsgDeduplicationNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/credentials/CertPemCredentialsTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/resources/pem/empty.pem create mode 100644 rule-engine/rule-engine-components/src/test/resources/pem/tb-cloud-chain.pem create mode 100644 rule-engine/rule-engine-components/src/test/resources/pem/tb-cloud.pem delete mode 100644 ui-ngx/.browserslistrc create mode 100644 ui-ngx/.eslintrc.json create mode 100644 ui-ngx/src/app/core/auth/auth.effects.ts create mode 100644 ui-ngx/src/app/core/http/alarm-comment.service.ts create mode 100644 ui-ngx/src/app/core/http/notification.service.ts create mode 100644 ui-ngx/src/app/core/http/user-settings.service.ts create mode 100644 ui-ngx/src/app/core/services/active-component.service.ts rename ui-ngx/src/app/core/services/{notification.service.ts => toast-notification.service.ts} (94%) create mode 100644 ui-ngx/src/app/core/ws/notification-websocket.service.ts create mode 100644 ui-ngx/src/app/core/ws/websocket.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/notification/notification-bell.component.html create mode 100644 ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.html create mode 100644 ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.html create mode 100644 ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/router-tabs.component.html rename ui-ngx/src/app/modules/home/{pages/entity-view/entity-view-table-header.component.scss => components/router-tabs.component.scss} (52%) create mode 100644 ui-ngx/src/app/modules/home/components/router-tabs.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss delete mode 100644 ui-ngx/src/app/modules/home/pages/device/device-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/entities/entities-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entities/entities.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/features/features.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.html rename ui-ngx/src/app/modules/home/{components/filter/filter-select.component.scss => pages/notification/inbox/inbox-notification-dialog.component.scss} (81%) create mode 100644 ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/notification.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html rename ui-ngx/src/app/modules/home/{components/widget/action/widget-action-dialog.component.scss => pages/notification/recipient/recipient-table-header.component.scss} (82%) create mode 100644 ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.html rename ui-ngx/src/app/{shared/components/entity/entity-autocomplete.component.scss => modules/home/pages/notification/rule/rule-table-header.component.scss} (80%) create mode 100644 ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.html rename ui-ngx/src/app/{shared/components/time/datetime.component.scss => modules/home/pages/notification/sent/sent-error-dialog.component.scss} (53%) create mode 100644 ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/sent/sent-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-configuration.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.html rename ui-ngx/src/app/modules/home/{components/profile/alarm/alarm-duration-predicate-value.component.scss => pages/notification/template/template-table-header.component.scss} (79%) create mode 100644 ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.ts create mode 100644 ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts delete mode 100644 ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.scss create mode 100644 ui-ngx/src/app/shared/components/markdown.component.scss delete mode 100644 ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts create mode 100644 ui-ngx/src/app/shared/components/notification/notification.component.html create mode 100644 ui-ngx/src/app/shared/components/notification/notification.component.scss create mode 100644 ui-ngx/src/app/shared/components/notification/notification.component.ts create mode 100644 ui-ngx/src/app/shared/components/notification/template-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/notification/template-autocomplete.component.scss create mode 100644 ui-ngx/src/app/shared/components/notification/template-autocomplete.component.ts create mode 100644 ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.html rename ui-ngx/src/app/shared/components/{entity/entity-list.component.scss => slack-conversation-autocomplete.component.scss} (73%) create mode 100644 ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.ts create mode 100644 ui-ngx/src/app/shared/components/string-items-list.component.html create mode 100644 ui-ngx/src/app/shared/components/string-items-list.component.ts create mode 100644 ui-ngx/src/app/shared/decorators/coerce-boolean.ts create mode 100644 ui-ngx/src/app/shared/models/api-usage.models.ts create mode 100644 ui-ngx/src/app/shared/models/id/alarm-comment-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/notification-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/notification-request-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/notification-rule-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/notification-target-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/notification-template-id.ts create mode 100644 ui-ngx/src/app/shared/models/notification.models.ts create mode 100644 ui-ngx/src/app/shared/models/user-settings.models.ts create mode 100644 ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts create mode 100644 ui-ngx/src/app/shared/models/websocket/websocket.models.ts create mode 100644 ui-ngx/src/app/shared/pipe/date-ago.pipe.ts create mode 100644 ui-ngx/src/assets/help/en_US/notification/alarm.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/alarm_comment.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/device_activity.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/entities_limit.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/entity_action.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/general.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/rule_engine.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md delete mode 100644 ui-ngx/src/assets/mdi.svg create mode 100644 ui-ngx/src/assets/notification-bell.svg create mode 100644 ui-ngx/src/theme-overwrites.scss create mode 100644 ui-ngx/src/theme/datepicker-theme.scss delete mode 100644 ui-ngx/tslint.json diff --git a/.github/release.yml b/.github/release.yml index 01ba7d863e..1d760fe7bf 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,5 +1,5 @@ # -# Copyright © 2016-2022 The Thingsboard Authors +# Copyright © 2016-2023 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/application/pom.xml b/application/pom.xml index 4d89099d5b..3093dcfb57 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ + + + + LWM2M Security + + 0 + urn:oma:lwm2m:oma:0:1.2 + 1.1 + 1.2 + Multiple + Mandatory + + + LWM2M Server URI + + Single + Mandatory + String + 0..255 + + + + + Bootstrap-Server + + Single + Mandatory + Boolean + + + + + + Security Mode + + Single + Mandatory + Integer + 0..4 + + + + + Public Key or Identity + + Single + Mandatory + Opaque + + + + + + Server Public Key + + Single + Mandatory + Opaque + + + + + + Secret Key + + Single + Mandatory + Opaque + + + + + + SMS Security Mode + + Single + Optional + Integer + 0..255 + + + + + SMS Binding Key Parameters + + Single + Optional + Opaque + 6 + + + + + SMS Binding Secret Key(s) + + Single + Optional + Opaque + 16,32,48 + + + + + LwM2M Server SMS Number + + Single + Optional + String + + + + + + Short Server ID + + Single + Optional + Integer + 1..65534 + + + + + Client Hold Off Time + + Single + Optional + Integer + + s + + + + Bootstrap-Server Account Timeout + + Single + Optional + Integer + + s + + + + Matching Type + + Single + Optional + Unsigned Integer + 0..3 + + + + + SNI + + Single + Optional + String + + + + + + Certificate Usage + + Single + Optional + Unsigned Integer + 0..3 + + + + + DTLS/TLS Ciphersuite + + Multiple + Optional + Unsigned Integer + + + + + OSCORE Security Mode + + Single + Optional + Objlnk + + + + + + Groups To Use by Client + + Multiple + Optional + Unsigned Integer + 0..65535 + + + + + Signature Algorithms Supported by Server + + Multiple + Optional + Unsigned Integer + 0..65535 + + + + Signature Algorithms To Use by Client + + Multiple + Optional + Unsigned Integer + 0..65535 + + + + + Signature Algorithm Certs Supported by Server + + Multiple + Optional + Unsigned Integer + 0..65535 + + + + + TLS 1.3 Features To Use by Client + + Single + Optional + Unsigned Integer + 0..65535 + + + + + TLS Extensions Supported by Server + + Single + Optional + Unsigned Integer + 0..65535 + + + + + TLS Extensions To Use by Client + + Single + Optional + Unsigned Integer + 0..65535 + + + + + Secondary LwM2M Server URI + + Multiple + Optional + String + 0..255 + + + + MQTT Server + + Single + Optional + Objlnk + + + + + LwM2M COSE Security + + Multiple + Optional + Objlnk + + + + + RDS Destination Port + + Single + Optional + Integer + 0..15 + + + + RDS Source Port + + Single + Optional + Integer + 0..15 + + + + RDS Application ID + + Single + Optional + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/1.xml b/application/src/main/data/lwm2m-registry/1.xml new file mode 100644 index 0000000000..ace9efcc6a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/1.xml @@ -0,0 +1,360 @@ + + + + + + + LwM2M Server + + 1 + urn:oma:lwm2m:oma:1:1.2 + 1.2 + 1.2 + Multiple + Mandatory + + + Short Server ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Lifetime + RW + Single + Mandatory + Integer + + s + + + + Default Minimum Period + RW + Single + Optional + Integer + + s + + + + Default Maximum Period + RW + Single + Optional + Integer + + s + + + + Disable + E + Single + Optional + + + + + + + Disable Timeout + RW + Single + Optional + Integer + + s + + + + Notification Storing When Disabled or Offline + RW + Single + Mandatory + Boolean + + + + + + Binding + RW + Single + Mandatory + String + + + + + + Registration Update Trigger + E + Single + Mandatory + + + + + + + Bootstrap-Request Trigger + E + Single + Optional + + + + + + + APN Link + RW + Single + Optional + Objlnk + + + + + + TLS-DTLS Alert Code + R + Single + Optional + Unsigned Integer + 0..255 + + + + + Last Bootstrapped + R + Single + Optional + Time + + + + + + Registration Priority Order + R + Single + Optional + Unsigned Integer + + + + + + Initial Registration Delay Timer + RW + Single + Optional + Unsigned Integer + + s + + + + Registration Failure Block + R + Single + Optional + Boolean + + + + + + Bootstrap on Registration Failure + R + Single + Optional + Boolean + + + + + + Communication Retry Count + RW + Single + Optional + Unsigned Integer + + + + + + Communication Retry Timer + RW + Single + Optional + Unsigned Integer + + s + + + + Communication Sequence Delay Timer + RW + Single + Optional + Unsigned Integer + + s + + + + Communication Sequence Retry Count + RW + Single + Optional + Unsigned Integer + + + + + + Trigger + RW + Single + Optional + Boolean + + + + + + Preferred Transport + RW + Single + Optional + String + The possible values are those listed in the LwM2M Core Specification + + + + Mute Send + RW + Single + Optional + Boolean + + + + + + Alternate APN Links + RW + Multiple + Optional + Objlnk + + + + + + Supported Server Versions + RW + Multiple + Optional + String + + + + + + Default Notification Mode + RW + Single + Optional + Integer + 0..1 + + + + + Profile ID Hash Algorithm + RW + Single + Optional + Unsigned Integer + 0..255 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10.xml b/application/src/main/data/lwm2m-registry/10.xml new file mode 100644 index 0000000000..af87413a94 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10.xml @@ -0,0 +1,217 @@ + + + + + + LWM2M Cellular Connectivity + + 10 + urn:oma:lwm2m:oma:10:1.1 + 1.1 + 1.1 + Single + Optional + + SMSC address + RW + Single + Optional + String + + + + + Disable radio period + RW + Single + Optional + Integer + 0..65535 + + 0 the device SHALL disconnect. When the period has elapsed the device MAY reconnect.]]> + + Module activation code + RW + Single + Optional + String + + + + + Vendor specific extensions + R + Single + Optional + Objlnk + + + + + PSM Timer + RW + Single + Optional + Integer + + s + + + Active Timer + RW + Single + Optional + Integer + + s + + + eDRX parameters for Iu mode + RW + Single + Optional + Opaque + 0..255 + + + + eDRX parameters for WB-S1 mode + RW + Single + Optional + Opaque + 0..255 + + + + eDRX parameters for NB-S1 mode + RW + Single + Optional + Opaque + 0..255 + + + + eDRX parameters for A/Gb mode + RW + Single + Optional + Opaque + 0..255 + + + + Activated Profile Names + R + Multiple + Mandatory + Objlnk + + + + + + Supported Power Saving Modes + R + Single + Optional + Unsigned Integer + 0..255 + + + + + Active Power Saving Modes + RW + Single + Optional + Unsigned Integer + 0..255 + + + + + Release Assistance Indication Usage + RW + Single + Optional + Unsigned Integer + 0..255 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10241.xml b/application/src/main/data/lwm2m-registry/10241.xml new file mode 100644 index 0000000000..5a499100f3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10241.xml @@ -0,0 +1,81 @@ + + + + + HostDeviceInfo + + 10241 + urn:oma:lwm2m:x:10241 + 1.0 + 1.0 + Multiple + Optional + + Host Device Manufacturer + R + Multiple + Mandatory + String + + + + + Host Device Model Number + R + Multiple + Mandatory + String + + + + + Host Device Unique ID + R + Multiple + Mandatory + String + + + + + Host Device Software Version + R + Multiple + Mandatory + String + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10242.xml b/application/src/main/data/lwm2m-registry/10242.xml new file mode 100644 index 0000000000..09a62806d3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10242.xml @@ -0,0 +1,659 @@ + + + + + + + 3-Phase Power Meter + + + + 10242 + urn:oma:lwm2m:x:10242 + 1.0 + 1.0 + Multiple + Optional + + + Manufacturer + R + Single + Optional + String + + + + + + + + Model Number + R + Single + Optional + String + + + + + + + + Serial Number + R + Single + Optional + String + + + + + + + + Description + R + Single + Optional + String + + + + + + + + Tension R + R + Single + Mandatory + Float + + V + + + + + + Current R + R + Single + Mandatory + Float + + A + + + + + + Active Power R + R + Single + Optional + Float + + kW + + + + + + Reactive Power R + R + Single + Optional + Float + + kvar + + + + + + Inductive Reactive Power R + R + Single + Optional + Float + + kvar + + + + + + Capacitive Reactive Power R + R + Single + Optional + Float + + kvar + + + + + + Apparent Power R + R + Single + Optional + Float + + kVA + + + + + + Power Factor R + R + Single + Optional + Float + -1..1 + + + + + + + THD-V R + R + Single + Optional + Float + + /100 + + + + + + THD-A R + R + Single + Optional + Float + + /100 + + + + + + Tension S + R + Single + Mandatory + Float + + V + + + + + + Current S + R + Single + Mandatory + Float + + A + + + + + + Active Power S + R + Single + Optional + Float + + kW + + + + + + Reactive Power S + R + Single + Optional + Float + + kvar + + + + + + Inductive Reactive Power S + R + Single + Optional + Float + + kvar + + + + + + Capacitive Reactive Power S + R + Single + Optional + Float + + kvar + + + + + + Apparent Power S + R + Single + Optional + Float + + kVA + + + + + + Power Factor S + R + Single + Optional + Float + -1..1 + + + + + + + THD-V S + R + Single + Optional + Float + + /100 + + + + + + THD-A S + R + Single + Optional + Float + + /100 + + + + + + Tension T + R + Single + Mandatory + Float + + V + + + + + + Current T + R + Single + Mandatory + Float + + A + + + + + + Active Power T + R + Single + Optional + Float + + kW + + + + + + Reactive Power T + R + Single + Optional + Float + + kvar + + + + + + Inductive Reactive Power T + R + Single + Optional + Float + + kvar + + + + + + Capacitive Reactive Power T + R + Single + Optional + Float + + kvar + + + + + + Apparent Power T + R + Single + Optional + Float + + kVA + + + + + + Power Factor T + R + Single + Optional + Float + -1..1 + + + + + + + THD-V T + R + Single + Optional + Float + + /100 + + + + + + THD-A T + R + Single + Optional + Float + + /100 + + + + + + 3-Phase Active Power + R + Single + Optional + Float + + kW + + + + + + 3-Phase Reactive Power + R + Single + Optional + Float + + kvar + + + + + + 3-Phase Inductive Reactive Power + R + Single + Optional + Float + + kvar + + + + + + 3-Phase Capacitive Reactive Power + R + Single + Optional + Float + + kvar + + + + + + 3-Phase Apparent Power + R + Single + Optional + Float + + kVA + + + + + + 3-Phase Power Factor + R + Single + Optional + Float + -1..1 + + + + + + + 3-Phase phi cosine + R + Single + Optional + Float + -1..1 + + + + + + + Active Energy + R + Single + Optional + Float + + kWh + + + + + + Reactive Energy + R + Single + Optional + Float + + kvarh + + + + + + Inductive Reactive Energy + R + Single + Optional + Float + + kvarh + + + + + + Capacitive Reactive Energy + R + Single + Optional + Float + + kvarh + + + + + + Apparent Energy + R + Single + Optional + Float + + kVAh + + + + + + Tension R-S + R + Single + Optional + Float + + V + + + + + + Tension S-T + R + Single + Optional + Float + + V + + + + + + Tension T-R + R + Single + Optional + Float + + V + + + + + + Frequency + R + Single + Optional + Float + + Hz + + + + + + Neutral Current + R + Single + Optional + Float + + A + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10243.xml b/application/src/main/data/lwm2m-registry/10243.xml new file mode 100644 index 0000000000..b73ce20021 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10243.xml @@ -0,0 +1,263 @@ + + + + + + + Single-Phase Power Meter + + + + 10243 + urn:oma:lwm2m:x:10243:2.0 + 1.0 + 2.0 + Multiple + Optional + + + Manufacturer + R + Single + Optional + String + + + + + + + + Model Number + R + Single + Optional + String + + + + + + + + Serial Number + R + Single + Optional + String + + + + + + + + Description + R + Single + Optional + String + + + + + + + + Tension + R + Single + Mandatory + Float + + V + + + + + + Current + R + Single + Mandatory + Float + + A + + + + + + Active Power + R + Single + Optional + Float + + kW + + + + + + Reactive Power + R + Single + Optional + Float + + kvar + + + + + + Inductive Reactive Power + R + Single + Optional + Float + + kvar + + + + + + Capacitive Reactive Power + R + Single + Optional + Float + + kvar + + + + + + Apparent Power + R + Single + Optional + Float + + kVA + + + + + + Power Factor + R + Single + Optional + Float + -1..1 + + + + + + + THD-V + R + Single + Optional + Float + + /100 + + + + + + THD-A + R + Single + Optional + Float + + /100 + + + + + + Active Energy + R + Single + Optional + Float + + kWh + + + + + + Reactive Energy + R + Single + Optional + Float + + kvarh + + + + + + Apparent Energy + R + Single + Optional + Float + + kVAh + + + + + + Frequency + R + Single + Optional + Float + + Hz + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10244.xml b/application/src/main/data/lwm2m-registry/10244.xml new file mode 100644 index 0000000000..9f55cd47de --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10244.xml @@ -0,0 +1,303 @@ + + + VehicleControlUnit + + 10244 + urn:oma:lwm2m:x:10244 + 1.0 + 1.0 + Single + Optional + + Vehicle UI State + R + Single + Mandatory + Integer + 0..15 + + + + Vehicle Speed + R + Single + Mandatory + Integer + + km/h + + + Vehicle Shift Status + R + Single + Mandatory + Integer + 0..3 + + + + Vehicle AP Position + R + Single + Mandatory + Integer + 0..100 + /100 + + + Vehicle Power + R + Single + Optional + Float + + kW + + + Vehicle Drive Energy + R + Single + Optional + Float + + Wh + + + Vehicle Energy Consumption Efficiency + R + Single + Optional + Float + + Wh/km + + + Vehicle Estimated Mileage + R + Single + Optional + Integer + + km + + + Vehicle Charge Cable Status + R + Single + Mandatory + Boolean + + + + + Vehicle Charge Status + R + Single + Mandatory + Integer + 0..15 + + + + Vehicle Charge Voltage + R + Single + Mandatory + Float + + V + + + Vehicle Charge Current + R + Single + Mandatory + Float + + A + + + Vehicle Charge Remaining Time + R + Single + Mandatory + Integer + + min + + + Battery Pack Voltage + R + Single + Mandatory + Float + + V + + + Battery Pack Current + R + Single + Mandatory + Float + + A + + + Battery Pack Remaining Capacity + R + Single + Mandatory + Integer + + Ah + + + Battery Pack SOC + R + Single + Mandatory + Integer + 0..100 + /100 + + + Battery Pack SOH + R + Single + Mandatory + Integer + 0..100 + /100 + + + Battery Cell MinVolt + R + Single + Mandatory + Integer + + mV + + + Battery Cell MaxVolt + R + Single + Mandatory + Integer + + mV + + + Battery Module MinTemp + R + Single + Mandatory + Integer + + Cel + + + Battery Module MaxTemp + R + Single + Mandatory + Integer + + Cel + + + Battery Connection Status + R + Single + Mandatory + Boolean + + + + + + MCU Voltage + R + Single + Mandatory + Integer + + V + + + MCU Temperature + R + Single + Mandatory + Integer + + Cel + + + Motor Speed + R + Single + Mandatory + Integer + + 1/min + + + Motor Temperature + R + Single + Mandatory + Integer + + Cel + + + Motor OT Warning + R + Single + Optional + Boolean + + + + + MCU OT Warning + R + Single + Optional + Boolean + + + + + Battery Pack OT Warning + R + Single + Optional + Boolean + + + + + MCU fault + R + Single + Optional + Boolean + + + + + Motor Error + R + Single + Optional + Boolean + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10245.xml b/application/src/main/data/lwm2m-registry/10245.xml new file mode 100644 index 0000000000..b29349130d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10245.xml @@ -0,0 +1,217 @@ + + + + + + Relay Management + This LWM2M Object provides a range of eNB related measurements and parameters of which several are changeable. Furthermore, it includes Resources to enable/disable the eNB. + 10245 + urn:oma:lwm2m:x:10245 + 1.0 + 1.0 + Single + Optional + + + eNB Availability + R + Single + Mandatory + Boolean + AVAILABLE; UNAVAILABLE + + This field indicates to the CCC whether or not the eNB of the CrowdBox is available for activation: AVAILABLE = TRUE; UNAVAILABLE = FALSE This is set by the CrowdBox itself using an algorithm specific to the use case and based on parameters known to the CrowdBox which may not necessarily be signalled to the network. In the absence of a more specific algorithm, this parameter should be set to AVAILABLE, unless a fault is detected which would prevent activation of the eNB, in which case it should be set to UNAVAILABLE. + + + GPS Status + R + Single + Mandatory + Boolean + UNSYNCHRONISED; SYNCHRONISED + + States whether the CrowdBox GPS receiver is synchronised to GPS time or not: UNSYCHRONISED = FALSE; SYNCHRONISED = TRUE If more than one GPS receiver is used by the CrowdBox, then SYNCHRONISED should be reported only if all receivers are synchronised. + + + Orientation + R + Single + Optional + Integer + -180..180 + deg + Orientation of CrowdBox with respect to magnetic north. The reference orientation of the CrowdBox shall be the pointing direction of the eNB antenna(s) or, in the case of an omni-directional CrowdBox antenna, as defined in the accompanying product documentation. + + + eNB EARFCN + RW + Single + Mandatory + Integer + 0..65535 + + EARFCN currently used by the eNB. Highest valid value in 3GPP is currently 46589. If the requested EARFCN is not supported by the eNB, the response should be "Bad Request". The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + eNB Bandwidth + RW + Single + Mandatory + Integer + 5, 10, 15, 20 + + Bandwidth of the currently used eNB carrier. If the requested bandwidth is not supported by the eNB, the response should be "Bad Request". The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Backhaul Primary EARFCN + RW + Single + Mandatory + Integer + 0..65535 + + EARFCN of primary cell used for the backhaul. If the requested EARFCN is not supported by the CrowdBox UE, the response should be "Bad Request". The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Backhaul Secondary EARFCN + RW + Multiple + Mandatory + Integer + 0..65535 + + EARFCN of any secondary cells used for the backhaul, in the event that carrier aggregation is being used. If the requested EARFCN is not supported by the CrowdBox UE, the response should be "Bad Request". The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Cumulative Measurement Window + RW + Single + Mandatory + Integer + 0..65535 + s + The current measurement interval over which cumulative statistics are collected for the following resources: Cumulative Number of Unique Users, Cumulative Downlink Throughput per Connected User, Cumulative Uplink Throughput per Connected User. Note that this measurement period is a sliding window rather than a granularity period. Measurements should never be reset, but rather old measurements should be removed from the cumulative total as they fall outside of the window. A value of 0 shall be interpreted as meaning only the current value should be reported. A value of 65535 shall be interpreted as an infinite window size (i.e. old measurements are never discarded). + + + eNB ECI + R + Single + Mandatory + Integer + 0..2^28-1 + + A 28 bit E-UTRAN Cell Identifier (ECI) + + + eNB Status + RW + Single + Mandatory + Boolean + + + This resource indicates the current status of the eNB and can be used by the CCC to change the state from enabled to disabled. TRUE = eNB enabled FALSE = eNB disabled + + + Enable eNB + E + Single + Mandatory + + + + Enables the eNB. In addition the CrowdBox shall also update its configuration to reflect the current state of other relevant parameters. This might require a reboot. + + + eNB Maximum Power + RW + Single + Mandatory + Integer + 0..63 + dBm + Maximum power for the eNB measured as the sum of input powers to all antenna connectors. The maximum power per antenna port is equal to the maximum eNB power divided by the number of antenna ports. If the requested power is above or below the maximum or minimum power levels of the eNB, then the power level should be set to the maximum or minimum respectively. The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Backhaul Primary q-OffsetFreq + RW + Single + Mandatory + Integer + -24..24 + dB + q-OffsetFreq parameter for the backhaul primary EARFCN in SIB5 of the CrowdBox eNB BCCH. See TS 36.331 for details. Range: dB-24; dB-22 .. dB24 The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Backhaul Secondary q-OffsetFreq + RW + Multiple + Mandatory + Integer + -24..24 + dB + q-OffsetFreq parameter for the backhaul secondary EARFCN in SIB5 of the CrowdBox eNB BCCH. See TS 36.331 for details Range: dB-24; dB-22 .. dB24 The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Neighbour CrowdBox EARFCN + RW + Multiple + Mandatory + Integer + 0..66635 + + EARFCN of a neighbour CrowdBox. Each instance of this resource relates to the same instance of resource ID 15. + + + Neighbour CrowdBox q-OffsetFreq + RW + Multiple + Mandatory + Integer + -24..24 + dB + q-OffsetFreq parameter of the Neighbour CrowdBox EARFCN in SIB5 of the Neighbour CrowdBox eNB BCCH. See TS 36.331 for details Range: dB-24; dB-22 .. dB24 Each instance of this resource relates to the same instance of resource ID 14. The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + Serving Macro eNB cellIndividualOffset + RW + Single + Mandatory + Integer + -24..24 + dB + Specifies the value of the cellIndividualOffset parameter applicable to the CrowdBox macro serving cell that is to be signalled to connected UEs in their measurement configuration information . See TS 36.331 for details. The CrowdBox shall only apply a change of this resource upon execution of the "Enable eNB" command. + + + + + diff --git a/application/src/main/data/lwm2m-registry/10246.xml b/application/src/main/data/lwm2m-registry/10246.xml new file mode 100644 index 0000000000..c3977d9538 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10246.xml @@ -0,0 +1,108 @@ + + + + + + + CrowdBox Measurements + This LWM2M Object provides CrowdBox-related measurements such as serving cell parameters, backhaul timing advance, and neighbour cell reports. + 10246 + urn:oma:lwm2m:x:10246 + 1.0 + 1.0 + Single + Optional + + + Serving Cell ID + R + Single + Mandatory + Integer + 0..2^32-1 + + Serving cell ID as specified by the cellIdentity field broadcast in SIB1 of the serving cell (see TS 36.331). + + + Serving Cell RSRP + R + Single + Mandatory + Integer + 0..97 + + Serving cell RSRP, as defined in TS 36.133, Section 9.1.4. Range: RSRP_00; RSRP_01 .. RSRP_97 + + + Serving Cell RSRQ + R + Single + Mandatory + Integer + -30..46 + + Serving cell RSRQ, as defined in TS 36.133, Section 9.1.7. Range: RSRQ_-30; RSRQ_-29 .. RSRQ_46 + + + Serving Cell SINR + R + Single + Mandatory + Integer + -10..30 + dB + SINR of serving cell as estimated by the CrowdBox. Note that this is a proprietary measurement dependent on the UE chipset manufacturer. The UE chipset used should be stated in the accompanying product documentation. + + + Cumulative Backhaul Timing Advance + R + Single + Optional + Integer + 0..65535 + + The cumulative timing advance signalled by the current serving cell to the CrowdBox. This is the sum of the initial timing advance signalled in the MAC payload of the Random Access Response (11 bits, 0 .. 1282) and subsequent adjustments signalled in the MAC PDU of DL-SCH transmissions (6 bits, -31 .. 32). See TS 36.321 for details. + + + Neighbour Cell Report + R + Multiple + Mandatory + Objlnk + + + A link to the "Neighbour Cell Report" object for each neighbour cell of the CrowdBox. + + + + + diff --git a/application/src/main/data/lwm2m-registry/10247.xml b/application/src/main/data/lwm2m-registry/10247.xml new file mode 100644 index 0000000000..eba05417e8 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10247.xml @@ -0,0 +1,98 @@ + + + + + + + Neighbour Cell Report + This LWM2M Object provides the neighbour cell report. The CrowdBox Measurements Object and the Connected UE Report Object have both Objlnk Resources pointing to this Object. + 10247 + urn:oma:lwm2m:x:10247 + 1.0 + 1.0 + Multiple + Optional + + + Neighbour PCI + R + Single + Mandatory + Integer + 0..503 + + Physical Cell ID of neighbouring LTE cell, as defined in TS 36.211 + + + Neighbour Cell ID + R + Single + Optional + Integer + 0..2^32-1 + + Neighbour cell ID as specified by the cellIdentity field broadcast in SIB1 of the neighbour cell (see TS 36.331). + + + Neighbour Cell Rank + R + Single + Mandatory + Integer + 0..255 + + Current neighbour cell rank. Neighbour cells should be ordered (ranked) by the CrowdBox according to neighbour cell RSRP, with a higher RSRP corresponding to a lower index. Hence the neighbouring cell with the highest RSRP should be neighbour cell 0, the second neighbour cell 1, and so on. + + + Neighbour Cell RSRP + R + Single + Mandatory + Integer + 0..97 + + Neighbour cell RSRP, as defined in TS 36.133, Section 9.1.4. Range: RSRP_00; RSRP_01 .. RSRP_97 + + + Neighbour Cell RSRQ + R + Single + Mandatory + Integer + -30..46 + + Neighbour cell RSRQ, as defined in TS 36.133, Section 9.1.7. Range: RSRQ_-30; RSRQ_-29 .. RSRQ_46 + + + + + diff --git a/application/src/main/data/lwm2m-registry/10248.xml b/application/src/main/data/lwm2m-registry/10248.xml new file mode 100644 index 0000000000..e9f69f7889 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10248.xml @@ -0,0 +1,78 @@ + + + + + + + Connected UE Measurements + This LWM2M Object provides a range of measurements of connected UEs and provides an Object link to the Connected UE report. + 10248 + urn:oma:lwm2m:x:10248 + 1.0 + 1.0 + Single + Optional + + + Number of Connected Users + R + Single + Mandatory + Integer + 0..255 + + The number of different UEs currently connected to the eNB (i.e. in RRC_CONNECTED state). + + + Cumulative Number of Unique Users + R + Single + Mandatory + Integer + 0..65535 + + The number of different UEs that have connected to the eNB over the immediately preceding period specified by the "Cumulative Measurement Window" field. + + + Connected UE Report + R + Multiple + Mandatory + Objlnk + + + Provides an Object link to the Connected UE Report which provides a range of information related to the connected UEs. + + + + + diff --git a/application/src/main/data/lwm2m-registry/10249.xml b/application/src/main/data/lwm2m-registry/10249.xml new file mode 100644 index 0000000000..e1257c3dda --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10249.xml @@ -0,0 +1,138 @@ + + + + + + + Connected UE Report + This LWM2M Object provides a range of information related to the connected UEs. + 10249 + urn:oma:lwm2m:x:10249 + 1.0 + 1.0 + Multiple + Optional + + + Connected User MMEC + R + Single + Mandatory + Integer + 0..255 + + MMEC signalled by the UE to the eNB in the RRCConnectionRequest message (see TS 36.331). + + + Connected User M-TMSI + R + Single + Mandatory + Integer + 0..2^32-1 + + M-TMSI signalled by the UE to the eNB in the RRCConnectionRequest message (see TS 36.331). + + + Serving Cell (CrowdBox) eNB RSRP + R + Single + Mandatory + Integer + 0..97 + + The RSRP of the CrowdBox eNB, as defined in TS 36.133, Section 9.1.4. Range: RSRP_00; RSRP_01 .. RSRP_97 + + + Serving Cell (CrowdBox) eNB RSRQ + R + Single + Mandatory + Integer + -30..46 + + The RSRQ of the CrowdBox eNB, as defined in TS 36.133, Section 9.1.7. Range: RSRQ_-30; RSRQ_-29 .. RSRQ_46 + + + Cumulative Timing Advance per Connected User + R + Single + Optional + Integer + 0..65535 + + The cumulative timing advance signalled by the eNB to each currently connected UE. This is the sum of the initial timing advance signalled in the MAC payload of the Random Access Response (11 bits, 0 .. 1282) and subsequent adjustments signalled in the MAC PDU of DL-SCH transmissions (6 bits, -31 .. 32). See TS 36.321 for details. + + + Last downlink CQI report per Connected User + R + Single + Mandatory + Integer + 0..255 + + The last downlink wideband CQI reported by a connected user the eNB. The CQI format is defined in Table 7.2.3-1 of TS 36.213. + + + Cumulative Downlink Throughput per Connected User + R + Single + Mandatory + Integer + 0..2^32-1 + B + The total number of MAC bytes sent to the connected user over the immediately preceding period specified by the "Cumulative Measurement Window" field. + + + Cumulative Uplink Throughput per Connected User + R + Single + Mandatory + Integer + 0..2^32-1 + B + The total number of MAC bytes received from the connected user over the immediately preceding period specified by the "Cumulative Measurement Window" field. + + + Neighbour Cell Report + R + Multiple + Mandatory + Objlnk + + + A link to the "Neighbour Cell Report" object for each neighbour cell reported to the CrowdBox by the connected UE + + + + + diff --git a/application/src/main/data/lwm2m-registry/10250.xml b/application/src/main/data/lwm2m-registry/10250.xml new file mode 100644 index 0000000000..015149fae2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10250.xml @@ -0,0 +1,70 @@ + + + + + App Data Container + + 10250 + urn:oma:lwm2m:x:10250 + 1.0 + 1.0 + Single + Optional + + + UL data + R + Single + Mandatory + Opaque + + + + + + + + DL data + W + Single + Mandatory + Opaque + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10251.xml b/application/src/main/data/lwm2m-registry/10251.xml new file mode 100644 index 0000000000..3fb1c00154 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10251.xml @@ -0,0 +1,96 @@ + + + + + + AT Command + + 10251 + urn:oma:lwm2m:x:10251 + 1.0 + 1.0 + Multiple + Optional + + + Command + RW + Single + Mandatory + String + + + + + + Response + R + Multiple + Mandatory + String + + + + + + Status + R + Multiple + Mandatory + String + + + + + + Timeout + RW + Single + Optional + Integer + + + + + + Run + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10252.xml b/application/src/main/data/lwm2m-registry/10252.xml new file mode 100644 index 0000000000..ddaf7cb1f2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10252.xml @@ -0,0 +1,229 @@ + + + + + Manifest + + 10252 + urn:oma:lwm2m:x:10252 + 1.0 + 1.0 + Single + Optional + + + Manifest + W + Single + Mandatory + Opaque + + + + + + + + State + R + Single + Mandatory + Integer + 0..8 + + + + + + Manifest Result + R + Single + Mandatory + Integer + 0..19 + + + + + + Payload Result + R + Single + Mandatory + Opaque + + + + + + + + Asset Hash + R + Single + Mandatory + Opaque + + + + + + + + Manifest version + R + Single + Mandatory + Integer + + + + + + + + Asset Installation Progress + R + Single + Mandatory + Integer + + + + + + + + Campaign Id + RW + Single + Mandatory + String + + + + + + + + Manual Trigger + E + Single + Mandatory + + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10253.xml b/application/src/main/data/lwm2m-registry/10253.xml new file mode 100644 index 0000000000..27da37d16a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10253.xml @@ -0,0 +1,72 @@ + + + + + + + Confidential Data + + 10253 + urn:oma:lwm2m:x:10253 + 1.0 + 1.0 + Single + Optional + + + Public Key + RW + Single + Mandatory + Opaque + + + + + + + + Application Data + R + Single + Mandatory + Opaque + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10254.xml b/application/src/main/data/lwm2m-registry/10254.xml new file mode 100644 index 0000000000..ea37cf8cd8 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10254.xml @@ -0,0 +1,119 @@ + + + + + Current Loop Input + + 10254 + urn:oma:lwm2m:x:10254:1.0 + 1.0 + 1.0 + Multiple + Optional + + + Current Loop Input Current Value + R + Single + Mandatory + Float + 0; 3.8..20.5 + mA + + + + Min Measured Value + R + Single + Optional + Float + + + + + + Max Measured Value + R + Single + Optional + Float + + + + + + Min Range Value + R + Single + Optional + Float + + + + + + Max Range Value + R + Single + Optional + Float + + + + + + Reset Min and Max Measured Values + E + Single + Optional + + + + + + + Sensor Units + R + Single + Optional + String + + + + + + Application Type + RW + Single + Optional + String + + + + + + Current Calibration + RW + Single + Optional + Float + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10255.xml b/application/src/main/data/lwm2m-registry/10255.xml new file mode 100644 index 0000000000..fd63016952 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10255.xml @@ -0,0 +1,165 @@ + + + + + Device Metadata + + 10255 + urn:oma:lwm2m:x:10255 + 1.0 + 1.0 + Single + Optional + + + Protocol supported + R + Single + Mandatory + Integer + + + + + + + + Bootloader hash + R + Single + Mandatory + Opaque + + + + + + + + OEM bootloader hash + R + Single + Mandatory + Opaque + + + + + + + + Vendor + R + Single + Mandatory + String + + + + + + + + Class + R + Single + Mandatory + String + + + + + + + + Device + R + Single + Mandatory + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10256.xml b/application/src/main/data/lwm2m-registry/10256.xml new file mode 100644 index 0000000000..dbcad740d0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10256.xml @@ -0,0 +1,117 @@ + + + + ECID-Signal Measurement Information + + 10256 + urn:oma:lwm2m:x:10256 + 1.0 + 1.0 + Multiple + Optional + + + physCellId + R + Single + Mandatory + Integer + + + + + + + + ECGI + R + Single + Optional + Integer + + + + + + + + arfcnEUTRA + R + Single + Mandatory + Integer + + + + + + + + rsrp-Result + R + Single + Mandatory + Integer + + + + + + + + rsrq-Result + R + Single + Optional + Integer + + + + + + + + ue-RxTxTimeDiff + R + Single + Optional + Integer + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10257.xml b/application/src/main/data/lwm2m-registry/10257.xml new file mode 100644 index 0000000000..75864d542b --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10257.xml @@ -0,0 +1,275 @@ + + + + Heat / Cooling meter + + 10257 + urn:oma:lwm2m:x:10257 + 1.0 + 1.0 + Multiple + Optional + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Description + R + Single + Optional + String + + + + + + Error code + R + Multiple + Optional + Integer + + + + + + + Instantaneous active power + R + Single + Optional + Float + + W + + + + Max Measured active power + R + Multiple + Mandatory + Float + + W + + + + Cumulative active power + R + Single + Optional + Float + + Wh + + + + Flow temperature + R + Single + Optional + Float + + Cel + + + + Max Measured flow temperature + R + Single + Optional + Float + + Cel + + + + Return temperature + R + Single + Optional + Float + + Cel + + + + Max Measured return temperature + R + Single + Optional + Float + + Cel + + + + Temperature difference + R + Single + Optional + Float + + K + + + + Flow rate + R + Single + Optional + Float + + m3/s + + + + Max Measured flow + R + Single + Optional + Float + + m3/s + + + + Flow volume + R + Single + Optional + Float + + m3 + + + + Return volume + R + Single + Optional + Float + + m3 + + + + Current Time + RW + Single + Optional + Time + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10258.xml b/application/src/main/data/lwm2m-registry/10258.xml new file mode 100644 index 0000000000..e2a9438170 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10258.xml @@ -0,0 +1,89 @@ + + + + + Current Loop Output + + 10258 + urn:oma:lwm2m:x:10258 + 1.0 + 1.0 + Multiple + Optional + + + Current Loop Output Current Value + RW + Single + Mandatory + Float + 3.8..20.5 + mA + + + + Min Range Value + R + Single + Optional + Float + + + + + + Max Range Value + R + Single + Optional + Float + + + + + + Sensor Units + R + Single + Optional + String + + + + + + Application Type + RW + Single + Optional + String + + + + + + Current Calibration + RW + Single + Optional + Float + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10259.xml b/application/src/main/data/lwm2m-registry/10259.xml new file mode 100644 index 0000000000..d75869a122 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10259.xml @@ -0,0 +1,115 @@ + + + + + + + System Log + + 10259 + urn:oma:lwm2m:x:10259 + 1.0 + 1.0 + Multiple + Optional + + + Name + R + Single + Mandatory + String + + + + + + + + Read All + R + Single + Mandatory + String + + + + + + + + Read + R + Single + Optional + String + + + + + + + + Enabled + RW + Single + Optional + Boolean + + + + + + + + Capture Level + RW + Single + Optional + Integer + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10260.xml b/application/src/main/data/lwm2m-registry/10260.xml new file mode 100644 index 0000000000..5cd084af73 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10260.xml @@ -0,0 +1,95 @@ + + + + + + RDB + + 10260 + urn:oma:lwm2m:x:10260:2.0 + 1.0 + 2.0 + Multiple + Optional + + + Key + RW + Single + Mandatory + String + + + + + + + + Value + RW + Single + Optional + String + + + + + + + + Exists + RW + Single + Optional + Boolean + + + + + + + + Persistent + RW + Single + Optional + Boolean + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10262.xml b/application/src/main/data/lwm2m-registry/10262.xml new file mode 100644 index 0000000000..9fd47ac3af --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10262.xml @@ -0,0 +1,89 @@ + + + + + + + Interval Data Delivery + + 10262 + urn:oma:lwm2m:x:10262 + 1.0 + 1.0 + Multiple + Optional + + + Name + RW + Single + Mandatory + String + + + + + + Interval Data Links + RW + Multiple + Mandatory + Objlnk + + + + + + Latest Payload + R + Multiple + Mandatory + Opaque + + + + + + Schedule + RW + Single + Optional + Objlnk + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10263.xml b/application/src/main/data/lwm2m-registry/10263.xml new file mode 100644 index 0000000000..07b9cde3ca --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10263.xml @@ -0,0 +1,92 @@ + + + + + + + Event Data Delivery + + 10263 + urn:oma:lwm2m:x:10263 + 1.0 + 1.0 + Multiple + Optional + + + Name + RW + Single + Mandatory + String + + + + + + Event Data Links + RW + Multiple + Mandatory + Objlnk + + + + + + Latest Eventlog + R + Multiple + Mandatory + Opaque + + + + + + Schedule + RW + Single + Mandatory + Objlnk + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10264.xml b/application/src/main/data/lwm2m-registry/10264.xml new file mode 100644 index 0000000000..b11c42a21d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10264.xml @@ -0,0 +1,119 @@ + + + + + + + Delivery Schedule + + 10264 + urn:oma:lwm2m:x:10264 + 1.0 + 1.0 + Multiple + Optional + + + Schedule Start Time + RW + Single + Mandatory + Integer + + + + + + Schedule UTC Offset + RW + Single + Mandatory + String + + + + + + Delivery Frequency + RW + Single + Mandatory + Integer + + + + + + Randomised Delivery Window + RW + Single + Optional + Integer + + + + + + Number of Retries + RW + Single + Optional + Integer + + + + + + Retry Period + RW + Single + Optional + Integer + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10265.xml b/application/src/main/data/lwm2m-registry/10265.xml new file mode 100644 index 0000000000..2604ae86d7 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10265.xml @@ -0,0 +1,138 @@ + + + + + + + Leakage Detection Configuration + + 10265 + urn:oma:lwm2m:x:10265 + 1.0 + 1.0 + Single + Optional + + + Sample Times + RW + Multiple + Mandatory + Integer + + + + + + Sample UTC Offset + RW + Single + Optional + String + + + + + + Detection Mode + RW + Single + Mandatory + Integer + 0..3 + + + + + Top Frequency Count + RW + Single + Optional + Integer + + + + + + Frequency Thresholds + RW + Multiple + Optional + Integer + 0..999 + + + + + Frequency Values + R + Multiple + Optional + Integer + + + + + + Firmware Version + R + Single + Mandatory + String + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10266.xml b/application/src/main/data/lwm2m-registry/10266.xml new file mode 100644 index 0000000000..48ee41319a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10266.xml @@ -0,0 +1,246 @@ + + + + + + + Water Flow Readings + + 10266 + urn:oma:lwm2m:x:10266 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10267.xml b/application/src/main/data/lwm2m-registry/10267.xml new file mode 100644 index 0000000000..ffe70b5f2f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10267.xml @@ -0,0 +1,246 @@ + + + + + + + Daily Maximum Flow Rate Readings + + 10267 + urn:oma:lwm2m:x:10267 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10268.xml b/application/src/main/data/lwm2m-registry/10268.xml new file mode 100644 index 0000000000..a1ccd43888 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10268.xml @@ -0,0 +1,246 @@ + + + + + + + Temperature Readings + + 10268 + urn:oma:lwm2m:x:10268 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10269.xml b/application/src/main/data/lwm2m-registry/10269.xml new file mode 100644 index 0000000000..e4156e055e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10269.xml @@ -0,0 +1,246 @@ + + + + + + + Pressure Readings + + 10269 + urn:oma:lwm2m:x:10269 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10270.xml b/application/src/main/data/lwm2m-registry/10270.xml new file mode 100644 index 0000000000..b7d171920c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10270.xml @@ -0,0 +1,246 @@ + + + + + + + Battery Level Readings + + 10270 + urn:oma:lwm2m:x:10270 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10271.xml b/application/src/main/data/lwm2m-registry/10271.xml new file mode 100644 index 0000000000..b4f8dfa6d5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10271.xml @@ -0,0 +1,246 @@ + + + + + + + Communications Activity Time Readings + + 10271 + urn:oma:lwm2m:x:10271 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10272.xml b/application/src/main/data/lwm2m-registry/10272.xml new file mode 100644 index 0000000000..0464c98a73 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10272.xml @@ -0,0 +1,232 @@ + + + + + + + Water Meter Customer Leakage Alarm + + 10272 + urn:oma:lwm2m:x:10272 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10273.xml b/application/src/main/data/lwm2m-registry/10273.xml new file mode 100644 index 0000000000..823adae0ed --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10273.xml @@ -0,0 +1,232 @@ + + + + + + + Water Meter Reverse Flow Alarm + + 10273 + urn:oma:lwm2m:x:10273 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10274.xml b/application/src/main/data/lwm2m-registry/10274.xml new file mode 100644 index 0000000000..f61554ca88 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10274.xml @@ -0,0 +1,232 @@ + + + + + + + Water Meter Empty Pipe Alarm + + 10274 + urn:oma:lwm2m:x:10274 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10275.xml b/application/src/main/data/lwm2m-registry/10275.xml new file mode 100644 index 0000000000..10248ea511 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10275.xml @@ -0,0 +1,232 @@ + + + + + + + Water Meter Tamper Alarm + + 10275 + urn:oma:lwm2m:x:10275 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10276.xml b/application/src/main/data/lwm2m-registry/10276.xml new file mode 100644 index 0000000000..5863f0326b --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10276.xml @@ -0,0 +1,232 @@ + + + + + + + Water Meter High Pressure Alarm + + 10276 + urn:oma:lwm2m:x:10276 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10277.xml b/application/src/main/data/lwm2m-registry/10277.xml new file mode 100644 index 0000000000..83c6d4f0d5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10277.xml @@ -0,0 +1,232 @@ + + + + + + + Water Meter Low Pressure Alarm + + 10277 + urn:oma:lwm2m:x:10277 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10278.xml b/application/src/main/data/lwm2m-registry/10278.xml new file mode 100644 index 0000000000..d004f89de6 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10278.xml @@ -0,0 +1,232 @@ + + + + + + + High Temperature Alarm + + 10278 + urn:oma:lwm2m:x:10278 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10279.xml b/application/src/main/data/lwm2m-registry/10279.xml new file mode 100644 index 0000000000..38838e7c94 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10279.xml @@ -0,0 +1,232 @@ + + + + + + + Low Temperature Alarm + + 10279 + urn:oma:lwm2m:x:10279 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10280.xml b/application/src/main/data/lwm2m-registry/10280.xml new file mode 100644 index 0000000000..de2b7b6732 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10280.xml @@ -0,0 +1,232 @@ + + + + + + + Water Network Leak Alarm + + 10280 + urn:oma:lwm2m:x:10280 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10281.xml b/application/src/main/data/lwm2m-registry/10281.xml new file mode 100644 index 0000000000..99eab667f2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10281.xml @@ -0,0 +1,232 @@ + + + + + + + Low Battery Alarm + + 10281 + urn:oma:lwm2m:x:10281 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10282.xml b/application/src/main/data/lwm2m-registry/10282.xml new file mode 100644 index 0000000000..e603de36b3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10282.xml @@ -0,0 +1,232 @@ + + + + + + + Daughter Board Failure Alarm + + 10282 + urn:oma:lwm2m:x:10282 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10283.xml b/application/src/main/data/lwm2m-registry/10283.xml new file mode 100644 index 0000000000..fd130fa713 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10283.xml @@ -0,0 +1,232 @@ + + + + + + + Device Reboot Event + + 10283 + urn:oma:lwm2m:x:10283 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10284.xml b/application/src/main/data/lwm2m-registry/10284.xml new file mode 100644 index 0000000000..1e889da48a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10284.xml @@ -0,0 +1,232 @@ + + + + + + + Time Synchronisation Event + + 10284 + urn:oma:lwm2m:x:10284 + 1.0 + 1.0 + Multiple + Optional + + + Event Type + RW + Single + Mandatory + Integer + + + + + + Alarm Realtime + RW + Single + Mandatory + Boolean + + + + + + Alarm State + R + Single + Optional + Boolean + + + + + + Alarm Set Threshold + RW + Single + Optional + Float + + + + + + Alarm Set Operator + RW + Single + Optional + Integer + + + + + + Alarm Clear Threshold + RW + Single + Optional + Float + + + + + + Alarm Clear Operator + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Count + RW + Single + Optional + Integer + + + + + + Alarm Maximum Event Period + RW + Single + Optional + Integer + 1..864000 + s + + + + Latest Delivered Event Time + RW + Single + Optional + Time + + + + + + Latest Recorded Event Time + R + Single + Mandatory + Time + + + + + + Alarm Clear + E + Single + Optional + + + + + + + Alarm Auto Clear + RW + Single + Optional + Boolean + + + + + + Event Code + R + Single + Mandatory + Integer + 100..255 + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10286.xml b/application/src/main/data/lwm2m-registry/10286.xml new file mode 100644 index 0000000000..39e0809788 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10286.xml @@ -0,0 +1,48 @@ + + + + App Fota Container + + 10286 + urn:oma:lwm2m:x:10286 + 1.0 + 1.0 + Single + Optional + + + UL data + R + Single + Mandatory + Opaque + + + + + + + + DL data + W + Single + Mandatory + Opaque + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10290.xml b/application/src/main/data/lwm2m-registry/10290.xml new file mode 100644 index 0000000000..7b89cc06c0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10290.xml @@ -0,0 +1,198 @@ + + + + + + + Voltage Logging + + 10290 + urn:oma:lwm2m:x:10290 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10291.xml b/application/src/main/data/lwm2m-registry/10291.xml new file mode 100644 index 0000000000..a232ed02f9 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10291.xml @@ -0,0 +1,208 @@ + + + + + + + Voltage Transient + + 10291 + urn:oma:lwm2m:x:10291 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Mandatory + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Mandatory + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + Sample Frequency + RW + Single + Mandatory + Float + 0.0..86400.0 + s + How often the inputs are read/sampled.This value can be changed by doing a write command + + + + + diff --git a/application/src/main/data/lwm2m-registry/10292.xml b/application/src/main/data/lwm2m-registry/10292.xml new file mode 100644 index 0000000000..00fe33e320 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10292.xml @@ -0,0 +1,208 @@ + + + + + + + Pressure Transient + + 10292 + urn:oma:lwm2m:x:10292 + 1.0 + 1.0 + Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Mandatory + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Mandatory + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + Sample Frequency + RW + Single + Mandatory + Float + 0.0..86400.0 + s + How often the inputs are read/sampled.This value can be changed by doing a write command + + + + + diff --git a/application/src/main/data/lwm2m-registry/10299.xml b/application/src/main/data/lwm2m-registry/10299.xml new file mode 100644 index 0000000000..a16a7c4fc2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10299.xml @@ -0,0 +1,113 @@ + + + + + + + HostDevice + This LWM2M Object provides a range of host device related information which can be queried by the LWM2M Server. The host device is any integrated device with an embedded cellular radio module. + 10299 + urn:oma:lwm2m:x:10299 + 1.0 + 1.0 + Single + Optional + + + Manufacturer + R + Single + Mandatory + String + + + Host device manufacturers name (OEM). + + + Model + R + Single + Mandatory + String + + Identifier of the model name or number determined by device manufacturer. + UniqueID + R + Single + Mandatory + String + + + Unique ID assigned by an manufacturer or other body. Used to uniquely identify a host device. Examples include serial # or UUID. + + + FirmwareVersion + R + Single + Mandatory + String + + + Current Firmware version of the host device. (manufacturer specified string). + + SoftwareVersion + R + Single + Optional + String + + + Current software version of the host device. (manufacturer specified string). + + HardwareVersion + R + Single + Optional + String + + + Current hardware version of the host device. (manufacturer specified string). + + + DateStamp + R + Single + Optional + String + + + UTC value of the time and date of the last Firmware or Software update. Format:MM:DD:YYYY HH:MM:SS + + + + + diff --git a/application/src/main/data/lwm2m-registry/10300.xml b/application/src/main/data/lwm2m-registry/10300.xml new file mode 100644 index 0000000000..234f0301a4 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10300.xml @@ -0,0 +1,142 @@ + + + + + + + LWM2M Meta Object + + + + 10300 + urn:oma:lwm2m:x:10300 + 1.0 + 1.0 + Multiple + Optional + + + ObjectID + R + Single + Mandatory + Integer + + + + + + + + ObjectURN + R + Single + Mandatory + String + + + + + + + + ObjectInstanceHandle + R + Single + Optional + Objlnk + + + + + + + + URI + R + Multiple + Mandatory + String + + + + + + + + SHAType + R + Single + Optional + Integer + 0..8 + + + + + + + ChecksumValue + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10308.xml b/application/src/main/data/lwm2m-registry/10308.xml new file mode 100644 index 0000000000..0ed1a6ea30 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10308.xml @@ -0,0 +1,145 @@ + + + + + + + AT&T Connectivity Extension + + 10308 + urn:oma:lwm2m:x:10308:2.0 + 1.0 + 2.0 + Multiple + Optional + + + ICCID + R + Single + Mandatory + String + + + + + + + IMSI + R + Single + Mandatory + String + + + + + + + MSISDN + RW + Single + Mandatory + String + + + + + + + APN Retries + RW + Single + Mandatory + Integer + + + + + + + APN Retry Period + RW + Single + Mandatory + Integer + + + s + + + + APN Retry Back-Off Period + RW + Single + Mandatory + Integer + + + s + + + + SINR + R + Single + Mandatory + Integer + <7 to >12.5 + + + + + SRXLEV + R + Single + Mandatory + Integer + + + + + + + CE_LEVEL + R + Single + Mandatory + Integer + 0,1,2 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10309.xml b/application/src/main/data/lwm2m-registry/10309.xml new file mode 100644 index 0000000000..b37b435724 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10309.xml @@ -0,0 +1,115 @@ + + + + + Shareparkinglot + + 10309 + urn:oma:lwm2m:x:10309 + 1.0 + 1.0 + Multiple + Optional + + + LockID + R + Single + Mandatory + Integer + + + + + + LockType + R + Single + Optional + String + + + + + + LightSwitchState + R + Multiple + Optional + Boolean + + + + + + RSSI + R + Multiple + Mandatory + Integer + -30..-120 + dBm + + + + BatteryCapacity + R + Multiple + Optional + Float + 0..100 + %EL + + + + DataUpTime + R + Multiple + Mandatory + Time + + + + + + Latitude + R + Multiple + Optional + String + + + + + + Longitude + R + Multiple + Optional + String + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10311.xml b/application/src/main/data/lwm2m-registry/10311.xml new file mode 100644 index 0000000000..6b6a78d21d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10311.xml @@ -0,0 +1,192 @@ + + + + + + + Solar Radiation + This Object is used to report solar irradiance (SI), i.e. power per unit area received from the Sun in the form of electromagnetic radiation, on a planar surface measured by a pyranometer or similar instrument. A pyranometer measures solar irradiance from the hemisphere above within a wavelength range 0.3 um to 3 um. For example, the application of solar radiation measurement can be meterological networks and solar energy applications. + 10311 + urn:oma:lwm2m:x:10311:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Min Measured Value + R + Single + Optional + Float + + + + The minimum value measured by the sensor since power ON or reset. + + + + Max Measured Value + R + Single + Optional + Float + + + + The maximum value measured by the sensor since power ON or reset. + + + + Min Range Value + R + Single + Optional + Float + + + + The minimum value that can be measured by the sensor. + + + + Max Range Value + R + Single + Optional + Float + + + + The maximum value that can be measured by the sensor. + + + + Reset Min and Max Measured Values + E + Single + Optional + + + + + Reset the Min and Max Measured Values to Current Value. + + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + + Measurement Units Definition. + + + + Application Type + RW + Single + Optional + String + + + + The application type of the sensor or actuator as a string depending on the use case. + + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/10313.xml b/application/src/main/data/lwm2m-registry/10313.xml new file mode 100644 index 0000000000..4875e5d6b0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10313.xml @@ -0,0 +1,253 @@ + + + + + + + Gas Readings + + 10313 + urn:oma:lwm2m:x:103131.0 + 1.0Multiple + Optional + + + Interval Period + R + Single + Mandatory + Integer + 1..864000 + s + + + + Interval Start Offset + R + Single + Optional + Integer + 0..86399 + s + + + + Interval UTC Offset + R + Single + Optional + String + + + + + + Interval Collection Start Time + R + Single + Mandatory + Time + + + + + + Oldest Recorded Interval + R + Single + Mandatory + Time + + + + + + Last Delivered Interval + RW + Single + Optional + Time + + + + + + Latest Recorded Interval + R + Single + Mandatory + Time + + + + + + Interval Delivery Midnight Aligned + RW + Single + Mandatory + Boolean + + + + + + Interval Historical Read + E + Single + Optional + + + + + + + Interval Historical Read Payload + R + Single + Optional + Opaque + + + + + + Interval Change Configuration + E + Single + Optional + + + + + + + Start + E + Single + Optional + + + + + + + Stop + E + Single + Optional + + + + + + + Status + R + Single + Optional + Integer + + + + + + Latest Payload + R + Single + Mandatory + Opaque + + + + + + Sensor Warm-up Time + RW + Single + Optional + Integer + 0..86400 + s + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10314.xml b/application/src/main/data/lwm2m-registry/10314.xml new file mode 100644 index 0000000000..f003fa03c2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10314.xml @@ -0,0 +1,106 @@ + + + + + + + Particulates + + 10314 + urn:oma:lwm2m:x:10314 + 1.0 + 1.0 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + + + + Min Measured Value + R + Single + Optional + Float + + + + + + Max Measured Value + R + Single + Optional + Float + + + + + + Max Range Value + R + Single + Optional + Float + + + + + + Sensor Units + R + Single + Optional + String + + + + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + + + + Measured Particle Size + R + Single + Mandatory + Float + + m + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10315.xml b/application/src/main/data/lwm2m-registry/10315.xml new file mode 100644 index 0000000000..da7ff8c67e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10315.xml @@ -0,0 +1,129 @@ + + + + + + Robot + + 10315 + urn:oma:lwm2m:x:10315 + 1.0 + 1.0 + Single + Mandatory + + + Robot ID + R + Single + Mandatory + String + 0..255 + + + + + Robot Type + R + Single + Mandatory + String + 0..63 + + + + + Robot Serial Number + R + Single + Mandatory + String + 0..63 + + + + + Battery Level + R + Single + Mandatory + Integer + 0..100 + % + + + + Charging + R + Single + Mandatory + Boolean + + + + + + On time + RW + Single + Mandatory + Integer + + s + + + + Positioning + R + Single + Optional + Boolean + + + + + + Location + R + Single + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10316.xml b/application/src/main/data/lwm2m-registry/10316.xml new file mode 100644 index 0000000000..d10dabf99b --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10316.xml @@ -0,0 +1,233 @@ + + + + + + RCU + + 10316 + urn:oma:lwm2m:x:10316 + 1.0 + 1.0 + Single + Mandatory + + + RCU ID + R + Single + Mandatory + String + 0..127 + + + + + RCU Serial Number + R + Single + Mandatory + String + 0..63 + + + + + RCU Software Version + R + Single + Mandatory + String + 0..63 + + + + + RCU OS Version + R + Single + Mandatory + String + 0..127 + + + + + RCU CPU Info + R + Single + Mandatory + String + 64 + + + + + RCU RAM Info + R + Single + Mandatory + String + 64 + + + + + RCU ROM Size + R + Single + Mandatory + Integer + + GB + + + + RCU ROM Available Size + R + Single + Mandatory + Integer + + GB + + + + SD Storage + R + Single + Mandatory + Integer + + GB + + + + SD Available Storage + R + Single + Mandatory + Integer + + GB + + + + RCU GPS Location + R + Single + Optional + Objlnk + + + + + + Wi-Fi MAC + R + Single + Optional + String + 12 + + + + + Bluetooth MAC + R + Single + Optional + String + 12 + + + + + Camera Info + R + Single + Optional + String + 64 + + + + + + Battery Level + R + Single + Mandatory + Integer + 0..100 + /100 + + + + + On time + RW + Single + Mandatory + Integer + + s + + + + + Downloaded APP Packages + R + Multiple + Mandatory + String + + + + + + + RCU APPs + R + Multiple + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10318.xml b/application/src/main/data/lwm2m-registry/10318.xml new file mode 100644 index 0000000000..989db0de7e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10318.xml @@ -0,0 +1,189 @@ + + + + + + RCU PM + + 10318 + urn:oma:lwm2m:x:10318 + 1.0 + 1.0 + Single + Mandatory + + + CPU Usage + R + Single + Mandatory + Integer + 0..100 + /100 + + + + Max CPU Usage + R + Single + Mandatory + Integer + 0..100 + /100 + + + + Memory Usage + R + Single + Mandatory + Integer + 0..100 + /100 + + + + Storage Usage + R + Single + Mandatory + Integer + 0..100 + /100 + + + + + Battery Level + R + Single + Mandatory + Integer + 0..100 + /100 + + + + Network Bandwidth + R + Single + Mandatory + Float + + Mbit/s + + + + Mobile Signal + R + Single + Mandatory + Integer + + + + + + GPS Signal + R + Single + Optional + Integer + + + + + + Wi-Fi Signal + R + Single + Mandatory + Integer + + + + + + UpLink Rate + R + Single + Mandatory + Float + + Mbit/s + + + + DownLink Rate + R + Single + Mandatory + Float + + Mbit/s + + + + Packet Loss Rate + R + Single + Mandatory + Integer + 0..100 + /100 + + + + Network Latency + R + Single + Mandatory + Integer + + ms + + + + + Battery Temperature + R + Single + Mandatory + Float + + Cel + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10319.xml b/application/src/main/data/lwm2m-registry/10319.xml new file mode 100644 index 0000000000..b8ebcef6fb --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10319.xml @@ -0,0 +1,130 @@ + + + + + + RCU Control + + 10319 + urn:oma:lwm2m:x:10319 + 1.0 + 1.0 + Single + Mandatory + + + RCU Diagnostics Mode + R + Single + Optional + Boolean + + + + + + RCU Log Recording + R + Single + Optional + Boolean + + + + + + + RCU Shutdown + E + Single + Mandatory + + + + + + + RCU Restart + E + Single + Mandatory + + + + + + + RCU Deactivate + E + Single + Mandatory + + + + + + + RCU Reset + E + Single + Mandatory + + + + + + + RCU Diagnostics Mode Control + E + Single + Mandatory + + + + + + + RCU Log Recording Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10320.xml b/application/src/main/data/lwm2m-registry/10320.xml new file mode 100644 index 0000000000..fb48c814f0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10320.xml @@ -0,0 +1,141 @@ + + + + + + CCU + + 10320 + urn:oma:lwm2m:x:10320 + 1.0 + 1.0 + Multiple + Optional + + + CCU ID + R + Single + Mandatory + String + + + + + + CCU FM Version + R + Single + Mandatory + String + + + + + + CCU SW Version + R + Single + Mandatory + String + + + + + + CCU Memory Size + R + Single + Mandatory + Integer + + GB + + + + CCU Storage + R + Single + Mandatory + Integer + + GB + + + + CCU Available Storage + R + Single + Mandatory + Integer + + GB + + + + + On time + RW + Single + Mandatory + Integer + + s + + + + + Downloaded APP Packages + R + Multiple + Optional + String + + + + + + CCU APPs + R + Multiple + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10322.xml b/application/src/main/data/lwm2m-registry/10322.xml new file mode 100644 index 0000000000..98822e0efe --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10322.xml @@ -0,0 +1,87 @@ + + + + + + CCU PM + + 10322 + urn:oma:lwm2m:x:10322 + 1.0 + 1.0 + Multiple + Optional + + + CPU Usage + R + Single + Optional + Integer + 0..100 + % + + + + Max CPU Usage + R + Single + Optional + Integer + 0..100 + % + + + + Memory Usage + R + Single + Optional + Integer + 0..100 + % + + + + Storage Usage + R + Single + Optional + Integer + 0..100 + % + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10323.xml b/application/src/main/data/lwm2m-registry/10323.xml new file mode 100644 index 0000000000..dcbdaeb91d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10323.xml @@ -0,0 +1,90 @@ + + + + + + CCU Control + + 10323 + urn:oma:lwm2m:x:10323 + 1.0 + 1.0 + Multiple + Optional + + + + CCU Restart + E + Single + Mandatory + + + + + + + CCU Reset + E + Single + Mandatory + + + + + + + CCU Self-checking + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10324.xml b/application/src/main/data/lwm2m-registry/10324.xml new file mode 100644 index 0000000000..b3db2b4f06 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10324.xml @@ -0,0 +1,77 @@ + + + + + + ECU + + 10324 + urn:oma:lwm2m:x:10324 + 1.0 + 1.0 + Multiple + Optional + + + ECU ID + R + Single + Mandatory + String + + + + + + ECU FM Version + R + Single + Mandatory + String + + + + + + On time + RW + Single + Mandatory + Integer + + s + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10326.xml b/application/src/main/data/lwm2m-registry/10326.xml new file mode 100644 index 0000000000..cdc01a0e61 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10326.xml @@ -0,0 +1,722 @@ + + + + + + Robot PM + + 10326 + urn:oma:lwm2m:x:10326 + 1.0 + 1.0 + Single + Mandatory + + + Battery Level + R + Single + Mandatory + Integer + 0..100 + /100 + , for example: ]]> + + + + Battery Temperature + R + Single + Mandatory + Integer + + Cel + + + + Temperature + R + Single + Optional + Float + + Cel + + + + Humidity + R + Single + Optional + Integer + 0..100 + /100 + + + + PM2.5 + R + Single + Optional + Integer + + ug/m3 + + + + Smog + R + Single + Optional + Float + + ug/m3 + + + + CO + R + Single + Optional + Float + + ppm + + + + CO2 + R + Single + Optional + Float + + ppm + + + + PM10 + R + Single + Optional + Integer + + ug/m3 + + + + Speed + R + Single + Optional + Float + + m/h + + + + Water Used + R + Single + Optional + Integer + 0..100 + /100 + + + + Dust Box Used + R + Single + Optional + Integer + 0..100 + /100 + + + + Obstacle Distance + R + Single + Optional + Integer + + cm + + + + + Robot Temperate + R + Single + Optional + Float + + Cel + + + + Confidence Index + R + Single + Optional + Integer + 0..100 + /100 + + + + + Data Traffic Used + R + Single + Mandatory + Float + + Mbit/s + + + + Images Handled + R + Single + Optional + Integer + + + + + + HARI S-Voice Requests + R + Single + Optional + Integer + + + + + + HARI S-Vision Requests + R + Single + Optional + Integer + + + + + + HARI S-Motion Requests + R + Single + Optional + Integer + + + + + + HARI S-Map Requests + R + Single + Optional + Integer + + + + + + Successful HARI S-Voice Requests + R + Single + Optional + Integer + + + + + + Successful HARI S-Vision Requests + R + Single + Optional + Integer + + + + + + Successful HARI S-Motion Requests + R + Single + Optional + Integer + + + + + + Successful HARI S-Map Requests + R + Single + Optional + Integer + + + + + + Questions Answered + R + Single + Optional + Integer + + + + + + Unknown Questions + R + Single + Optional + Integer + + + + + + Mileage + R + Single + Optional + Integer + + m + + + + Cleaned Times + R + Single + Optional + Integer + + + + + + Cleaned Area + R + Single + Optional + Float + + m2 + + + + Cleaned Time + R + Single + Optional + Integer + + s + + + + ASR Recognized + R + Single + Optional + Integer + + B + + + + Incorrect ASR Recognitions + R + Single + Optional + Integer + + B + + + + Tried TTS Texts + R + Single + Optional + Integer + + B + + + + Successful TTS Texts + R + Single + Optional + Integer + + B + + + + ASR Recognized (CH) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (CH) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (CH) + R + Single + Optional + Integer + + B + + + + ASR Recognized (EN) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (EN) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (EN) + R + Single + Optional + Integer + + B + + + + ASR Recognized (ES) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (ES) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (ES) + R + Single + Optional + Integer + + B + + + + ASR Recognized (JA) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (JA) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (JA) + R + Single + Optional + Integer + + B + + + + ASR Recognized (SCCH) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (SCCH) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (SCCH) + R + Single + Optional + Integer + + B + + + + ASR Recognized (GDCH) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (GDCH) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (GDCH) + R + Single + Optional + Integer + + B + + + + ASR Recognized (TCH) + R + Single + Optional + Integer + + B + + + + Tried TTS Texts (TCH) + R + Single + Optional + Integer + + B + + + + Successful TTS Texts (TCH) + R + Single + Optional + Integer + + B + + + + + Objects Recognition Tries + R + Single + Optional + Integer + + + + + + Successful Object Recognition + R + Single + Optional + Integer + + + + + + Face Recognition Tries + R + Single + Optional + Integer + + + + + + Successful Face Recognitions + R + Single + Optional + Integer + + + + + + Vehicle Plate Recognition Tries + R + Single + Optional + Integer + + + + + + Successful Vehicle Plate Recognitions + R + Single + Optional + Integer + + + + + + Tasks Assigned + R + Single + Mandatory + Integer + + + + + + Successful Tasks Executed + R + Single + Mandatory + Integer + + + + + + Images Uploaded + R + Single + Optional + Integer + + + + + + Videos Uploaded + R + Single + Optional + Integer + + + + + + Images Matted + R + Single + Optional + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10327.xml b/application/src/main/data/lwm2m-registry/10327.xml new file mode 100644 index 0000000000..5f0e232a22 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10327.xml @@ -0,0 +1,68 @@ + + + + + + Compressor + + 10327 + urn:oma:lwm2m:x:10327 + 1.0 + 1.0 + Multiple + Optional + + + Compressor Name + R + Single + Mandatory + String + + + + + + + Compressor Status + R + Single + Mandatory + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10328.xml b/application/src/main/data/lwm2m-registry/10328.xml new file mode 100644 index 0000000000..fdf39d5dc1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10328.xml @@ -0,0 +1,80 @@ + + + + + + SCA PM + + 10328 + urn:oma:lwm2m:x:10328 + 1.0 + 1.0 + Multiple + Optional + + + SCA Name + R + Single + Mandatory + String + + + + + + + + + SCA Current + R + Single + Mandatory + Float + + A + + + + SCA Temperate + R + Single + Mandatory + Float + + Cel + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10329.xml b/application/src/main/data/lwm2m-registry/10329.xml new file mode 100644 index 0000000000..7470bec6e1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10329.xml @@ -0,0 +1,433 @@ + + + + + + Robot Control + + 10329 + urn:oma:lwm2m:x:10329 + 1.0 + 1.0 + Single + Mandatory + + + Collision Detection + R + Single + Optional + Boolean + + + + + + Drop Detection + R + Single + Optional + Boolean + + + + + + Automatic Navigation + R + Single + Optional + Boolean + + + + + + Robot Shutdown + E + Single + Mandatory + + + + + + + Robot Reboot + E + Single + Mandatory + + + + + + + Robot Reset + E + Single + Mandatory + + + + + + + Robot Wakeup + E + Single + Mandatory + + + + + + + Robot Sleep + E + Single + Mandatory + + + + + + + Robot Self-checking + E + Single + Mandatory + + + + + + + Emergency Braking + E + Single + Mandatory + + + + + + + Emergency Braking Release + E + Single + Mandatory + + + + + + + Action Execution + E + Single + Optional + + + + + + + Action List Upload + E + Single + Optional + + + + + + + Action List Download + E + Single + Optional + + + + + + + Group Dancing Program Control + E + Single + Optional + + + + + + + Navigation Map Upload + E + Single + Optional + + + + + + + Group Dancing Program Control + E + Single + Optional + + + + + + + Navigation Map Download + E + Single + Optional + + + + + + + Route list Execution + E + Single + Optional + + + + + + + Route list Upload + E + Single + Optional + + + + + + + Route list Download + E + Single + Optional + + + + + + + Automatic Navigation Control + E + Single + Optional + + + + + + + Manual Navigation + E + Single + Optional + + + + + + + Moving to Charging Station + E + Single + Optional + + + + + + + Moving to Specified location + E + Single + Optional + + + + + + + Low Frequency Patrol Broadcasting + E + Single + Optional + + + + + + + Task Start + E + Single + Optional + + + + + + + Task Stop + E + Single + Optional + + + + + + + Task Suspend + E + Single + Optional + + + + + + + Task Resume + E + Single + Optional + + + + + + + Video Upload + E + Single + Optional + + + + + + + Picture Upload + E + Single + Optional + + + + + + + Default Language Switching + E + Single + Optional + + + + + + + Intonation Change + E + Single + Optional + + + + + + + Intonation Change + E + Single + Optional + + + + + + + Speaking with Action + E + Single + Optional + + + + + + + Collision Detection Control + E + Single + Mandatory + + + + + + + Drop Detection Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10330.xml b/application/src/main/data/lwm2m-registry/10330.xml new file mode 100644 index 0000000000..75c5a2ecdd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10330.xml @@ -0,0 +1,120 @@ + + + + + + Network Info + + 10330 + urn:oma:lwm2m:x:10330 + 1.0 + 1.0 + Single + Mandatory + + + IMEI + R + Single + Mandatory + String + 15 + + + + + IMSI + R + Single + Mandatory + String + 15 + + + + + Radio Connectivity + R + Single + Mandatory + Objlnk + + + + + + + + GPS Signal Status + R + Single + Optional + Integer + 1..4 + + + + + VBN Connection Status + R + Single + Mandatory + Integer + 0..1 + + + + + HARI Connection Status + R + Single + Mandatory + Integer + 0..1 + + + + + CCU Connection Status + R + Multiple + Optional + Integer + 0..1 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10331.xml b/application/src/main/data/lwm2m-registry/10331.xml new file mode 100644 index 0000000000..ca555cc0d9 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10331.xml @@ -0,0 +1,221 @@ + + + + + + Robot Service Info + + 10331 + urn:oma:lwm2m:x:10331 + 1.0 + 1.0 + Single + Mandatory + + + Current status + R + Single + Mandatory + String + 0..127 + + + + + Services Providing + R + Single + Optional + String + 0..127 + + + + + Advertising Contents + R + Single + Mandatory + String + + + + + + Current Language + R + Single + Optional + String + 0..127 + + + + + Volume + R + Single + Optional + String + 0..100 + /100 + + + + Moving Status + R + Single + Optional + Integer + 0..2 + + + + + Moving Speed + R + Single + Optional + Float + + m/h + + + + Location + R + Single + Optional + Objlnk + + + + + + + + Map List + R + Single + Optional + String + + + + + + Planned Route list + R + Single + Optional + String + + + + + + Current Route + R + Single + Optional + String + + + + + + Route to-do List + R + Single + Optional + String + + + + + + Synchronous Whistle + R + Single + Mandatory + Boolean + + + + + + Current Actions + R + Single + Optional + String + + + + + + ASR Type + R + Single + Optional + Integer + 0..2 + + + + + + TTS Vendor + R + Single + Optional + String + + + + + + TTS Speaker + R + Single + Optional + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10332.xml b/application/src/main/data/lwm2m-registry/10332.xml new file mode 100644 index 0000000000..f582373043 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10332.xml @@ -0,0 +1,69 @@ + + + + + + Robot Selfcheck Info + + 10332 + urn:oma:lwm2m:x:10332 + 1.0 + 1.0 + Multiple + Optional + + + Entity + R + Single + Mandatory + String + 4..63 + + + .]]> + + + + Status + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10333.xml b/application/src/main/data/lwm2m-registry/10333.xml new file mode 100644 index 0000000000..aaeb25487d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10333.xml @@ -0,0 +1,88 @@ + + + + + + PM Threshold + + 10333 + urn:oma:lwm2m:x:10333 + 1.0 + 1.0 + Single + Optional + + + Entity + RW + Multiple + Mandatory + String + 4..63 + + .]]> + + + Performance Type + RW + Multiple + Mandatory + String + + + , for example:/10322/2/4]]> + + + High Threshold + RW + Multiple + Mandatory + Float + + + + + + Low Threshold + RW + Multiple + Mandatory + Float + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10334.xml b/application/src/main/data/lwm2m-registry/10334.xml new file mode 100644 index 0000000000..479406630c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10334.xml @@ -0,0 +1,129 @@ + + + + + + Robot Alarm + + 10334 + urn:oma:lwm2m:x:10334 + 1.0 + 1.0 + Multiple + Optional + + + Entity + R + Single + Mandatory + String + 4..63 + + .]]> + + + Probable Cause + R + Single + Mandatory + Integer + 0..65535 + + + + + Specific Problem + R + Single + Mandatory + String + + + + + + Alarm Type + R + Single + Mandatory + Integer + 2..6 + + + + + Severity + R + Single + Mandatory + Integer + 1..5 + + + + + Report Time + R + Single + Mandatory + Time + + + + + + Sequence No + R + Single + Mandatory + Integer + 0..2^63-1 + + + + + Additional Info + R + Single + Optional + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10335.xml b/application/src/main/data/lwm2m-registry/10335.xml new file mode 100644 index 0000000000..91fe8cd9c5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10335.xml @@ -0,0 +1,97 @@ + + + + + + Event + + 10335 + urn:oma:lwm2m:x:10335 + 1.0 + 1.0 + Multiple + Optional + + + Entity + R + Single + Mandatory + String + 4..63 + + .]]> + + + Event Type + R + Single + Mandatory + Integer + 0..65535 + + + + + Time + R + Single + Mandatory + Time + + + + + + Sequence No + R + Single + Mandatory + Integer + 0..2^63-1 + + + + + Additional Info + R + Single + Optional + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10336.xml b/application/src/main/data/lwm2m-registry/10336.xml new file mode 100644 index 0000000000..5016e6ee13 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10336.xml @@ -0,0 +1,84 @@ + + + + + + MIC + + 10336 + urn:oma:lwm2m:x:10336 + 1.0 + 1.0 + Single + Optional + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10337.xml b/application/src/main/data/lwm2m-registry/10337.xml new file mode 100644 index 0000000000..6204020400 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10337.xml @@ -0,0 +1,118 @@ + + + + + + SCA + + 10337 + urn:oma:lwm2m:x:10337 + 1.0 + 1.0 + Multiple + Optional + + + SCA Name + R + Single + Mandatory + String + + + + + + + + + + SCA Motion Status + R + Single + Optional + Integer + 0..2 + + + + + + SCA Motion Control + E + Single + Optional + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10338.xml b/application/src/main/data/lwm2m-registry/10338.xml new file mode 100644 index 0000000000..c66df8addf --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10338.xml @@ -0,0 +1,124 @@ + + + + + + Speaker + + 10338 + urn:oma:lwm2m:x:10338 + 1.0 + 1.0 + Single + Optional + + + Speaker status + R + Single + Mandatory + Boolean + + + + + + Voice Warning + R + Single + Mandatory + Boolean + + + + + + + Voice Control + E + Single + Mandatory + + + + + + + Voice Warning Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10339.xml b/application/src/main/data/lwm2m-registry/10339.xml new file mode 100644 index 0000000000..5376de4a6c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10339.xml @@ -0,0 +1,114 @@ + + + + + + Tripod Head + + 10339 + urn:oma:lwm2m:x:10339 + 1.0 + 1.0 + Single + Optional + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + Tripod Direction Control + E + Single + Optional + + + + + + + Tripod Automatic Control + E + Single + Optional + + + + + + + Tripod Reset + E + Single + Optional + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10340.xml b/application/src/main/data/lwm2m-registry/10340.xml new file mode 100644 index 0000000000..679b46f150 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10340.xml @@ -0,0 +1,218 @@ + + + + + + Camera + + 10340 + urn:oma:lwm2m:x:10340 + 1.0 + 1.0 + Multiple + Optional + + + Camera Name + R + Single + Mandatory + String + + + + + + + + Camera Status + R + Single + Mandatory + Boolean + + + + + + Connection Status + R + Single + Mandatory + Boolean + + + + + + Working Status + R + Single + Mandatory + Integer + 0..15 + + + + + Local Recording + R + Single + Mandatory + Boolean + + + + + + Image Matting + R + Single + Mandatory + Boolean + + + + + + Camera Snapshot + R + Single + Mandatory + Boolean + + + + + + Camera Recording + R + Single + Mandatory + Boolean + + + + + + + Camera Control + E + Single + Mandatory + + + + + + + Local Recording Control + E + Single + Mandatory + + + + + + + Image Matting Control + E + Single + Mandatory + + + + + + + Camera Snapshot Control + E + Single + Mandatory + + + + + + + Camera Recording Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10341.xml b/application/src/main/data/lwm2m-registry/10341.xml new file mode 100644 index 0000000000..c632fd5c57 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10341.xml @@ -0,0 +1,84 @@ + + + + + + GPS + + 10341 + urn:oma:lwm2m:x:10341 + 1.0 + 1.0 + Single + Optional + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10342.xml b/application/src/main/data/lwm2m-registry/10342.xml new file mode 100644 index 0000000000..644bcf9071 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10342.xml @@ -0,0 +1,84 @@ + + + + + + IMU + + 10342 + urn:oma:lwm2m:x:10342 + 1.0 + 1.0 + Single + Optional + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10343.xml b/application/src/main/data/lwm2m-registry/10343.xml new file mode 100644 index 0000000000..a34c80f3a6 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10343.xml @@ -0,0 +1,96 @@ + + + + + + LiDAR + + 10343 + urn:oma:lwm2m:x:10343 + 1.0 + 1.0 + Multiple + Optional + + + LiDAR Name + R + Single + Mandatory + String + + + + + + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10344.xml b/application/src/main/data/lwm2m-registry/10344.xml new file mode 100644 index 0000000000..d28d343ae3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10344.xml @@ -0,0 +1,96 @@ + + + + + + Arm + + 10344 + urn:oma:lwm2m:x:10344 + 1.0 + 1.0 + Multiple + Optional + + + Arm Name + R + Single + Mandatory + String + + + + + + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10345.xml b/application/src/main/data/lwm2m-registry/10345.xml new file mode 100644 index 0000000000..ad489a7faa --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10345.xml @@ -0,0 +1,96 @@ + + + + + + Leg + + 10345 + urn:oma:lwm2m:x:10345 + 1.0 + 1.0 + Multiple + Optional + + + Leg Name + R + Single + Mandatory + String + + + + + + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10346.xml b/application/src/main/data/lwm2m-registry/10346.xml new file mode 100644 index 0000000000..4ee595e16a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10346.xml @@ -0,0 +1,96 @@ + + + + + + Servomotor + + 10346 + urn:oma:lwm2m:x:10346 + 1.0 + 1.0 + Multiple + Optional + + + Servomotor Name + R + Single + Mandatory + String + + + + + + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10347.xml b/application/src/main/data/lwm2m-registry/10347.xml new file mode 100644 index 0000000000..6c0808ed61 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10347.xml @@ -0,0 +1,154 @@ + + + + + + Screen + + 10347 + urn:oma:lwm2m:x:10347 + 1.0 + 1.0 + Single + Optional + + + Screen Status + R + Single + Mandatory + Boolean + + + + + + Startup Page + R + Single + Optional + String + + + The Startup Page of the screen. + + + Current Displaying Page or Current Screen Play List + R + Single + Optional + String + + + Current Displaying Page or Current Screen Play List. + + + + Screen Control + E + Single + Mandatory + + + + + + + Startup Page Set + E + Single + Mandatory + + + + + + + Screen Page Set + E + Single + Mandatory + + + + + + + Screen Play List Set + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10348.xml b/application/src/main/data/lwm2m-registry/10348.xml new file mode 100644 index 0000000000..76451605f0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10348.xml @@ -0,0 +1,96 @@ + + + + + + Wheel + + 10348 + urn:oma:lwm2m:x:10348 + 1.0 + 1.0 + Multiple + Optional + + + Wheel Name + R + Single + Mandatory + String + + + + + + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10349.xml b/application/src/main/data/lwm2m-registry/10349.xml new file mode 100644 index 0000000000..a4212558c4 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10349.xml @@ -0,0 +1,84 @@ + + + + + + Chassis + + 10349 + urn:oma:lwm2m:x:10349 + 1.0 + 1.0 + Single + Optional + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10350.xml b/application/src/main/data/lwm2m-registry/10350.xml new file mode 100644 index 0000000000..1942cabe0c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10350.xml @@ -0,0 +1,116 @@ + + + + + + Light + + 10350 + urn:oma:lwm2m:x:10350 + 1.0 + 1.0 + Multiple + Optional + + + Light Name + R + Single + Mandatory + String + + + + + + + + Light Status + R + Single + Mandatory + Boolean + + + + + + + Light Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10351.xml b/application/src/main/data/lwm2m-registry/10351.xml new file mode 100644 index 0000000000..473b440b02 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10351.xml @@ -0,0 +1,79 @@ + + + + + + Door + + 10351 + urn:oma:lwm2m:x:10351 + 1.0 + 1.0 + Multiple + Optional + + + Door Name + R + Single + Mandatory + String + + + + + + + Door Status + R + Single + Mandatory + Boolean + + + + + + + Door Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10352.xml b/application/src/main/data/lwm2m-registry/10352.xml new file mode 100644 index 0000000000..1e7503ef42 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10352.xml @@ -0,0 +1,114 @@ + + + + + + Thermal Imager + + 10352 + urn:oma:lwm2m:x:10352 + 1.0 + 1.0 + Single + Optional + + + + Highest Temperature + R + Single + Mandatory + Float + -100.0..100.0 + Cel + The Highest Temperature of the thermal imager. + + + Lowest Temperature + R + Single + Mandatory + Float + -100.0..100.0 + Cel + The Lowest Temperature of the thermal imager. + + + Average Temperature + R + Single + Mandatory + Float + -100.0..100.0 + Cel + The Average Temperature of the thermal imager. + + + + + diff --git a/application/src/main/data/lwm2m-registry/10353.xml b/application/src/main/data/lwm2m-registry/10353.xml new file mode 100644 index 0000000000..0b6bb56cc4 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10353.xml @@ -0,0 +1,87 @@ + + + + + + Warning Light + + 10353 + urn:oma:lwm2m:x:10353 + 1.0 + 1.0 + Single + Optional + + + Light Status + R + Single + Mandatory + Boolean + + + + + + Light Warning + R + Single + Mandatory + Boolean + + + + + + Light Control + E + Single + Mandatory + + + + + + + Light Warning Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10354.xml b/application/src/main/data/lwm2m-registry/10354.xml new file mode 100644 index 0000000000..60d5fc0050 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10354.xml @@ -0,0 +1,217 @@ + + + + + + APP + + 10354 + urn:oma:lwm2m:x:10354 + 1.0 + 1.0 + Multiple + Mandatory + + + APP Name + RW + Single + Mandatory + String + + + The name of the APP, human readable string. + + + APP Version + RW + Single + Mandatory + String + + + The version of the APP, human readable string. + + + APP Build No + RW + Single + Optional + String + + + The Build No of the APP, human readable string. + + + APP Patch No + RW + Single + Optional + String + + + The Patch No of the APP, human readable string. + + + Package URI + W + Single + Optional + String + 0..255 bytes + + + + + Vendor Name + RW + Single + Optional + String + + + The vendor of the package. + + + Installation Target + RW + Single + Mandatory + Objlnk + + + + + + APP Status + R + Single + Mandatory + Integer + 0..5 + + The Status of the APP, 0:Downloading, 1:Downloaded, 2:Installed, 3:Verified, 4:Activated, 5:Stopped. + + + APP Restart + E + Single + Mandatory + + + + + + + APP Start + E + Single + Mandatory + + + + + + + APP Stop + E + Single + Mandatory + + + + + + + APP Download + E + Single + Mandatory + + + + + + + APP Install + E + Single + Mandatory + + + + + + + APP Uninstall + E + Single + Mandatory + + + + + + + APP Activate + E + Single + Mandatory + + + + + + + APP Deactivate + E + Single + Mandatory + + + + + + + APP Verify + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10355.xml b/application/src/main/data/lwm2m-registry/10355.xml new file mode 100644 index 0000000000..fb38ee2767 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10355.xml @@ -0,0 +1,95 @@ + + + + + + General Info + + 10355 + urn:oma:lwm2m:x:10355 + 1.0 + 1.0 + Single + Optional + + + Robot General Info + R + Single + Mandatory + Objlnk + + + + + + + + RCU General Info + R + Single + Mandatory + Objlnk + + + + + + + + CCU General Info + R + Multiple + Optional + Objlnk + + + + + + + + ECU General Info + R + Multiple + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10356.xml b/application/src/main/data/lwm2m-registry/10356.xml new file mode 100644 index 0000000000..2e362ce052 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10356.xml @@ -0,0 +1,203 @@ + + + + + + Service Info + + 10356 + urn:oma:lwm2m:x:10356 + 1.0 + 1.0 + Single + Optional + + + Robot Service Info + R + Single + Mandatory + Objlnk + + + + + + + + SCA Info + R + Multiple + Optional + Objlnk + + + + + + + + Speaker Info + R + Single + Optional + Objlnk + + + + + + + + Camera Info + R + Multiple + Optional + Objlnk + + + + + + + + Screen Info + R + Single + Optional + Objlnk + + + + + + + + Light Info + R + Multiple + Optional + Objlnk + + + + + + + + Warning Light Info + R + Single + Optional + Objlnk + + + + + + + + Door Info + R + Multiple + Optional + Objlnk + + + + + + + + Thermal Imager Info + R + Single + Optional + Objlnk + + + + + + + + Compressor Info + R + Multiple + Optional + Objlnk + + + + + + + + Lock Info + R + Multiple + Optional + Objlnk + + + + + + + + Collision Sensor Info + R + Multiple + Optional + Objlnk + + + + + + + + Drop Sensor Info + R + Multiple + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10357.xml b/application/src/main/data/lwm2m-registry/10357.xml new file mode 100644 index 0000000000..9487b2813d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10357.xml @@ -0,0 +1,95 @@ + + + + + + PM + + 10357 + urn:oma:lwm2m:x:10357 + 1.0 + 1.0 + Single + Optional + + + Robot PM + R + Single + Mandatory + Objlnk + + + + + + + + RCU PM + R + Single + Mandatory + Objlnk + + + + + + + + CCU PM + R + Multiple + Optional + Objlnk + + + + + + + + SCA PM + R + Multiple + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10358.xml b/application/src/main/data/lwm2m-registry/10358.xml new file mode 100644 index 0000000000..27a3684b22 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10358.xml @@ -0,0 +1,70 @@ + + + + + + Fan PM + + 10358 + urn:oma:lwm2m:x:10358 + 1.0 + 1.0 + Multiple + Optional + + + Fan Name + R + Single + Mandatory + String + + + + + + + + + Fan Speed + R + Single + Optional + Integer + + 1/min + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10359.xml b/application/src/main/data/lwm2m-registry/10359.xml new file mode 100644 index 0000000000..2fc8af9677 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10359.xml @@ -0,0 +1,78 @@ + + + + + + Lock + + 10359 + urn:oma:lwm2m:x:10359 + 1.0 + 1.0 + Multiple + Optional + + + Lock Name + R + Single + Mandatory + String + + + + + + Lock Status + R + Single + Optional + Boolean + + + + + + + Lock Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10360.xml b/application/src/main/data/lwm2m-registry/10360.xml new file mode 100644 index 0000000000..935fea848c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10360.xml @@ -0,0 +1,70 @@ + + + + + + Ultrasonic Sensor + + 10360 + urn:oma:lwm2m:x:10360 + 1.0 + 1.0 + Multiple + Optional + + + Name + R + Single + Mandatory + String + + + + + + + + + Status + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10361.xml b/application/src/main/data/lwm2m-registry/10361.xml new file mode 100644 index 0000000000..3525647afd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10361.xml @@ -0,0 +1,92 @@ + + + + + + Collision Sensor + + 10361 + urn:oma:lwm2m:x:10361 + 1.0 + 1.0 + Multiple + Optional + + + Name + R + Single + Mandatory + String + + + + + + + + Status + R + Single + Mandatory + Integer + + + + + + + Collision Detection + R + Single + Optional + Boolean + + + + + + + Collision Detection Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10362.xml b/application/src/main/data/lwm2m-registry/10362.xml new file mode 100644 index 0000000000..8a928e4425 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10362.xml @@ -0,0 +1,91 @@ + + + + + + Drop Sensor + + 10362 + urn:oma:lwm2m:x:10362 + 1.0 + 1.0 + Multiple + Optional + + + Name + R + Single + Mandatory + String + + + + + + + + Status + R + Single + Mandatory + Integer + + + + + + Drop Detection + R + Single + Optional + Boolean + + + + + + + Drop Detection Control + E + Single + Mandatory + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10363.xml b/application/src/main/data/lwm2m-registry/10363.xml new file mode 100644 index 0000000000..ca9d064f2f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10363.xml @@ -0,0 +1,57 @@ + + + + + + Temperature Sensor + + 10363 + urn:oma:lwm2m:x:10363 + 1.0 + 1.0 + Single + Optional + + + Status + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10364.xml b/application/src/main/data/lwm2m-registry/10364.xml new file mode 100644 index 0000000000..3519665e62 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10364.xml @@ -0,0 +1,57 @@ + + + + + + Humidity Sensor + + 10364 + urn:oma:lwm2m:x:10364 + 1.0 + 1.0 + Single + Optional + + + Status + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10365.xml b/application/src/main/data/lwm2m-registry/10365.xml new file mode 100644 index 0000000000..792844271e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10365.xml @@ -0,0 +1,57 @@ + + + + + + Gas-Dust Sensor + + 10365 + urn:oma:lwm2m:x:10365 + 1.0 + 1.0 + Single + Optional + + + Status + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10366.xml b/application/src/main/data/lwm2m-registry/10366.xml new file mode 100644 index 0000000000..44e94ae51d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10366.xml @@ -0,0 +1,59 @@ + + + + + + Fan + + 10366 + urn:oma:lwm2m:x:10366 + 1.0 + 1.0 + Multiple + Optional + + + Fan Name + R + Single + Mandatory + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10368.xml b/application/src/main/data/lwm2m-registry/10368.xml new file mode 100644 index 0000000000..68d3dd3ee3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10368.xml @@ -0,0 +1,96 @@ + + + + + + SpringMotor + + 10368 + urn:oma:lwm2m:x:10368 + 1.0 + 1.0 + Multiple + Optional + + + SpringMotor Name + R + Single + Mandatory + String + + + + + + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10369.xml b/application/src/main/data/lwm2m-registry/10369.xml new file mode 100644 index 0000000000..dcf8190bda --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10369.xml @@ -0,0 +1,84 @@ + + + + + + MCU + + 10369 + urn:oma:lwm2m:x:10369 + 1.0 + 1.0 + Single + Optional + + + Host Device Unique ID + R + Single + Optional + String + + + + + + + + Host Device Manufacturer + R + Single + Optional + String + + + + + + + + Host Device Model Number + R + Single + Optional + String + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10371.xml b/application/src/main/data/lwm2m-registry/10371.xml new file mode 100644 index 0000000000..73a4476865 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10371.xml @@ -0,0 +1,85 @@ + + + + + Reboot Status + + 10371 + urn:oma:lwm2m:x:10371 + 1.0 + 1.0 + Single + Optional + + + Reboot State + R + Single + Mandatory + Integer + 0..2 + + + + + Reboot Error Message + R + Single + Mandatory + String + + + + + + Reset Factory State + R + Single + Optional + Integer + 0..3 + + + + + Reset Factory Error Message + R + Single + Optional + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10374.xml b/application/src/main/data/lwm2m-registry/10374.xml new file mode 100644 index 0000000000..a51646b95f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10374.xml @@ -0,0 +1,161 @@ + + + + Modbus Connection + + 10374 + urn:oma:lwm2m:x:10374 + 1.0 + 1.0 + Multiple + Optional + + + Address + RW + Single + Mandatory + String + + + + + + Physical Layer Type + RW + Single + Mandatory + Integer + 0..2 + + + + + Enable + RW + Single + Mandatory + Boolean + + + + + + Baudrate + RW + Single + Optional + Integer + + bit/s + + + + Stop Bits + RW + Single + Optional + Integer + 1..2 + + + + + Parity + RW + Single + Optional + Integer + 0..2 + + + + + Hardware Control Flow Mode + RW + Single + Optional + Integer + 0..2 + + + + + Available RTU Ports + R + Multiple + Optional + String + + + + + + State + R + Single + Mandatory + Integer + 0..4 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10375.xml b/application/src/main/data/lwm2m-registry/10375.xml new file mode 100644 index 0000000000..5818491c51 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10375.xml @@ -0,0 +1,148 @@ + + + + Modbus Register Cluster + + 10375 + urn:oma:lwm2m:x:10375 + 1.0 + 1.0 + Multiple + Optional + + + Connection + RW + Single + Mandatory + Objlnk + + + + + + Register Type + RW + Single + Mandatory + Integer + 1..4 + + + + + Unit ID + RW + Single + Mandatory + Integer + 0..247, 255 + + + + + Starting Register Address + RW + Single + Mandatory + Integer + 0..65535 + + + + + Quantity of Registers + RW + Single + Optional + Integer + 1..2000 + + + + + Values + RW + Multiple + Mandatory + Integer + 0..65535 + + + + + Minimum Measured Values + R + Multiple + Optional + Integer + 0..65535 + + + + + Maximum Measured Values + R + Multiple + Optional + Integer + 0..65535 + + + + + Reset Minimum and Maximum Measured Values + E + Single + Optional + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10376.xml b/application/src/main/data/lwm2m-registry/10376.xml new file mode 100644 index 0000000000..faec95398e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10376.xml @@ -0,0 +1,211 @@ + + + + + Periodic Activity + + + + 10376 + urn:oma:lwm2m:x:10376 + 1.0 + 1.0 + Multiple + Optional + + + Activity Name + R + Single + Mandatory + String + + + + + + + Activity Description + R + Single + Mandatory + String + + + + + + + Activity Settings + RW + Single + Optional + Opaque + + + + + + + Start Mask + RW + Single + Mandatory + String + + + + + + Periodic Interval + RW + Single + Mandatory + Integer + + s + + + + + Run Period + RW + Single + Optional + Integer + + s + + + + + Record + R + Single + Optional + Opaque + + + + + + Record Head Index + R + Single + Optional + Integer + + + + + + + Record Tail Index + R + Single + Optional + Integer + + + + + + + Record Read Index + RW + Single + Optional + Integer + + + + + + + Record Dispatch Start Mask + RW + Single + Optional + String + + + + + + Record Dispatch Interval + RW + Single + Optional + Integer + + s + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10377.xml b/application/src/main/data/lwm2m-registry/10377.xml new file mode 100644 index 0000000000..f46ace72aa --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10377.xml @@ -0,0 +1,251 @@ + + + + + Data Monitoring + + 10377 + urn:oma:lwm2m:x:10377 + 1.0 + 1.0 + Multiple + Optional + + + Monitoring Name + R + Single + Mandatory + String + + + + + + + Monitoring Description + R + Single + Mandatory + String + + + + + + + Monitoring Settings + RW + Single + Optional + Opaque + + + + + + + Data + R + Single + Mandatory + Opaque + + + + + + + Data Sampling Start Mask + RW + Single + Optional + String + + + + + + Data Sampling Interval + RW + Single + Optional + Integer + + s + + + + Data Sampling Run Period + RW + Single + Optional + Integer + + s + + + + Reference A + RW + Single + Mandatory + Opaque + + + + + + + Comparison A + RW + Single + Mandatory + Integer + + + Reference A +2: Data >= Reference A +3: Data < Reference A +4: Data <= Reference A +5: Data equals Reference A +6: Data not equal Reference A +]]> + + + Reference B + RW + Single + Optional + Opaque + + + + + + + Comparison B + RW + Single + Optional + Integer + + + Reference B +2: Data >= Reference B +3: Data < Reference B +4: Data <= Reference B +5: Data equals Reference B +6: Data not equal Reference B +]]> + + + Reference C + RW + Single + Optional + Opaque + + + + + + + Comparison C + RW + Single + Optional + Integer + + + Reference C +2: Data >= Reference C +3: Data < Reference C +4: Data <= Reference C +5: Data equals Reference C +6: Data not equal Reference C +]]> + + + Monitoring Results + RW + Single + Mandatory + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/10378.xml b/application/src/main/data/lwm2m-registry/10378.xml new file mode 100644 index 0000000000..bbd45811d0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/10378.xml @@ -0,0 +1,91 @@ + + + + + Edge Application Server Configuration + + 10378 + urn:oma:lwm2m:x:10378 + 1.0 + 1.0 + Multiple + Optional + + + Application Client ID + RW + Single + Mandatory + String + + + + + + EAS ID + RW + Multiple + Mandatory + String + + + + + + EAS Endpoint + RW + Multiple + Mandatory + String + + + + + + Type + RW + Multiple + Optional + Integer + 1..2 + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/11.xml b/application/src/main/data/lwm2m-registry/11.xml new file mode 100644 index 0000000000..438b2c3df2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/11.xml @@ -0,0 +1,408 @@ + + + + + LWM2M APN Connection Profile + + 11 + urn:oma:lwm2m:oma:11:1.1 + 1.1 + 1.1 + Multiple + Optional + + Profile name + RW + Single + Mandatory + String + + + + + APN + RW + Single + Optional + String + + + + + Auto select APN by device + RW + Single + Optional + Boolean + + + + + Enable status + RW + Single + Optional + Boolean + + + + + Authentication Type + RW + Single + Mandatory + Integer + + + + + User Name + RW + Single + Optional + String + + + + + Secret + RW + Single + Optional + String + + + + + Reconnect Schedule + RW + Single + Optional + String + + + + + Validity (MCC, MNC) + RW + Multiple + Optional + String + + + + + Connection establishment time (1) + R + Multiple + Optional + Time + + + + + Connection establishment result (1) + R + Multiple + Optional + Integer + + + + + + Connection establishment reject cause (1) + R + Multiple + Optional + Integer + 0..111 + + + + Connection end time (1) + R + Multiple + Optional + Time + + + + + TotalBytesSent + R + Single + Optional + Integer + + + + + TotalBytesReceived + R + Single + Optional + Integer + + + + + IP address (2) + RW + Multiple + Optional + String + + + + + Prefix length(2) + RW + Multiple + Optional + String + + + + + Subnet mask (2) + RW + Multiple + Optional + String + + + + + Gateway (2) + RW + Multiple + Optional + String + + + + + Primary DNS address (2) + RW + Multiple + Optional + String + + + + + Secondary DNS address (2) + RW + Multiple + Optional + String + + + + + QCI (3) + R + Single + Optional + Integer + 1..9 + + + + Vendor specific extensions + R + Single + Optional + Objlnk + + + + + TotalPacketsSent + R + Single + Optional + Integer + + + + + PDN Type + RW + Single + Optional + Integer + + + + + + APN Rate Control + R + Single + Optional + Integer + + + + + + Serving PLMN Rate control + RW + Single + Optional + Unsigned Integer + + + + + Uplink Time Unit + RW + Single + Optional + Unsigned Integer + 0..4 + + + + APN Rate Control for Exception Data + R + Single + Optional + Integer + + + + + + APN Exception Data Uplink Time Unit + RW + Single + Optional + Unsigned Integer + 0..4 + + + + Supported RAT Types + RW + Multiple + Optional + Unsigned Integer + + + + + RDS Application ID + RW + Multiple + Optional + String + + + + + RDS Destination Port + RW + Multiple + Optional + Integer + 0..15 + + + + RDS Source Port + RW + Multiple + Optional + Integer + 0..15 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/12.xml b/application/src/main/data/lwm2m-registry/12.xml new file mode 100644 index 0000000000..d4aad43dca --- /dev/null +++ b/application/src/main/data/lwm2m-registry/12.xml @@ -0,0 +1,551 @@ + + + + + + + WLAN connectivity + + 12 + urn:oma:lwm2m:oma:12:1.1 + 1.0 + 1.1 + Multiple + Optional + + Interface name + RW + Single + Mandatory + String + + + + + Enable + RW + Single + Mandatory + Boolean + + + + + Radio Enabled + RW + Single + Optional + Integer + + + + + Status + R + Single + Mandatory + Integer + + + + + BSSID + R + Single + Mandatory + String + 12 + + + + SSID + RW + Single + Mandatory + String + 1..32 + + + + Broadcast SSID + RW + Single + Optional + Boolean + + + + + Beacon Enabled + RW + Single + Optional + Boolean + + + + + Mode + RW + Single + Mandatory + Integer + + + + + Channel + RW + Single + Mandatory + Integer + 0..255 + + + + Auto Channel + RW + Single + Optional + Boolean + + + + + Supported Channels + RW + Multiple + Optional + Integer + + + + + Channels In Use + RW + Multiple + Optional + Integer + + + + + Regulatory Domain + RW + Single + Optional + String + 3 + + + + Standard + RW + Single + Mandatory + Integer + + + + + Authentication Mode + RW + Single + Mandatory + Integer + + + + + Encryption Mode + RW + Single + Optional + Integer + + + + + WPA Pre Shared Key + W + Single + Optional + String + 64 + + + + WPA Key Phrase + W + Single + Optional + String + 1..64 + + + + WEP Encryption Type + RW + Single + Optional + Integer + + + + + WEP Key Index + RW + Single + Optional + Integer + 1..4 + + + + WEP Key Phrase + W + Single + Optional + String + 1..64 + + + + WEP Key 1 + W + Single + Optional + String + 10,26 + + + + WEP Key 2 + W + Single + Optional + String + 10,26 + + + + WEP Key 3 + W + Single + Optional + String + 10,26 + + + + WEP Key 4 + W + Single + Optional + String + 10,26 + + + + RADIUS Server + RW + Single + Optional + String + 1..256 + + + + RADIUS Server Port + RW + Single + Optional + Integer + + + + + RADIUS Secret + W + Single + Optional + String + 1..256 + + + + WMM Supported + R + Single + Optional + Boolean + + + + + WMM Enabled + RW + Single + Optional + Boolean + + + + + MAC Control Enabled + RW + Single + Optional + Boolean + + + + + MAC Address List + RW + Multiple + Optional + String + 12 + + + + Total Bytes Sent + R + Single + Optional + Integer + + + + + Total Bytes Received + R + Single + Optional + Integer + + + + + Total Packets Sent + R + Single + Optional + Integer + + + + + Total Packets Received + R + Single + Optional + Integer + + + + + Transmit Errors + R + Single + Optional + Integer + + + + + Receive Errors + R + Single + Optional + Integer + + + + + Unicast Packets Sent + R + Single + Optional + Integer + + + + + Unicast Packets Received + R + Single + Optional + Integer + + + + + Multicast Packets Sent + R + Single + Optional + Integer + + + + + Multicast Packets Received + R + Single + Optional + Integer + + + + + Broadcast Packets Sent + R + Single + Optional + Integer + + + + + Broadcast Packets Received + R + Single + Optional + Integer + + + + + Discard Packets Sent + R + Single + Optional + Integer + + + + + Discard Packets Received + R + Single + Optional + Integer + + + + + Unknown Packets Received + R + Single + Optional + Integer + + + + + Vendor specific extensions + R + Single + Optional + Objlnk + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/13.xml b/application/src/main/data/lwm2m-registry/13.xml new file mode 100644 index 0000000000..9b30ecd225 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/13.xml @@ -0,0 +1,209 @@ + + + + + LWM2M Bearer Selection + + 13 + urn:oma:lwm2m:oma:13:1.1 + 1.1 + 1.1 + Single + Optional + + Preferred Communications Bearer + RW + Multiple + Optional + Integer + 0..100 + + + + Acceptable RSSI (GSM) + RW + Single + Optional + Integer + -110..-48 + + + + Acceptable RSCP (UMTS) + RW + Single + Optional + Integer + -120..-25 + + + + Acceptable RSRP (LTE) + RW + Single + Optional + Integer + -140..-44 + + + + Acceptable RSSI (1xEV-DO) + RW + Single + Optional + Integer + + + + + Cell lock list + RW + Single + Optional + String + + + + + Operator list + RW + Single + Optional + String + + + + + Operator list mode + RW + Single + Optional + Boolean + + + + + List of available PLMNs + R + Single + Optional + String + + + + + Vendor specific extensions + R + Single + Optional + Objlnk + + + + + Acceptable RSRP (NB-IoT) + RW + Single + Optional + Integer + -158..-44 + + + + Higher Priority PLMN Search Timer + RW + Single + Optional + Integer + + + + + Attach without PDN connection + RW + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/14.xml b/application/src/main/data/lwm2m-registry/14.xml new file mode 100644 index 0000000000..a1d7f7239f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/14.xml @@ -0,0 +1,139 @@ + + + + + + + LWM2M Software Component + + 14 + urn:oma:lwm2m:oma:14 + 1.0 + 1.0 + Multiple + Optional + + + Component Identity + R + Single + Optional + String + 0..255 bytes + + + + + Component Pack + R + Single + Optional + Opaque + + + + + + Component Version + R + Single + Optional + String + 0..255 bytes + + + + + Activate + E + Single + Optional + + + + + + + Deactivate + E + Single + Optional + + + + + + + Activation State + R + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/15.xml b/application/src/main/data/lwm2m-registry/15.xml new file mode 100644 index 0000000000..f4363e3f33 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/15.xml @@ -0,0 +1,175 @@ + + + + + + + DevCapMgmt + + 15 + urn:oma:lwm2m:oma:15:2.0 + 1.0 + 2.0 + Multiple + Optional + + Property + R + Single + Mandatory + String + + + + + Group + R + Single + Mandatory + Integer + 0..15 + + + + Description + R + Single + Optional + String + + + + + Attached + R + Single + Optional + Boolean + + + + + Enabled + R + Single + Mandatory + Boolean + + + + + opEnable + E + Single + Mandatory + + + + + + opDisable + E + Single + Mandatory + + + + + + NotifyEn + RW + Single + Optional + Boolean + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/16.xml b/application/src/main/data/lwm2m-registry/16.xml new file mode 100644 index 0000000000..f8f2dc40df --- /dev/null +++ b/application/src/main/data/lwm2m-registry/16.xml @@ -0,0 +1,127 @@ + + + + + + + Portfolio + + 16 + urn:oma:lwm2m:oma:161.0 + 1.0Multiple + Optional + + Identity + RW + Multiple + Mandatory + String + + + + + GetAuthData + E + Single + Optional + + + + + + AuthData + R + Multiple + Optional + Opaque + + + + + AuthStatus + R + Single + Optional + Integer + 0..2 + + + + + + diff --git a/application/src/main/data/lwm2m-registry/18830.xml b/application/src/main/data/lwm2m-registry/18830.xml new file mode 100644 index 0000000000..87e0747d5c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/18830.xml @@ -0,0 +1,173 @@ + + + + MQTT Broker + + 18830 + urn:oma:lwm2m:x:18830 + 1.1 + 1.0 + Multiple + Optional + + + URI + RW + Single + Mandatory + String + + + + + + Client Identifier + RW + Single + Mandatory + String + + + + + + Clean Session + RW + Single + Mandatory + Boolean + + + + + + Keep Alive + RW + Single + Mandatory + Unsigned Integer + 0..65535 + s + + + + User Name + RW + Single + Optional + String + + + + + + Password + RW + Single + Optional + Opaque + 0..65535 + + + + + Security Mode + RW + Single + Mandatory + Integer + 0..4 + + + + + Public Key or Identity + RW + Single + Optional + Opaque + + + + + + MQTT Broker Public Key + RW + Single + Optional + Opaque + + + + + + Secret Key + W + Single + Optional + Opaque + + + + + + Certificate Usage + RW + Single + Optional + Integer + 0..3 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/18831.xml b/application/src/main/data/lwm2m-registry/18831.xml new file mode 100644 index 0000000000..68dc873ed0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/18831.xml @@ -0,0 +1,120 @@ + + + + MQTT Publication + This LwM2M Object is used to configure data reporting over MQTT. + 18831 + urn:oma:lwm2m:x:18831 + 1.1 + 1.0 + Multiple + Optional + + + Source + RW + Single + Mandatory + Corelnk + + + , or ; ). If this Resource is empty, the published data are implementation dependent.]]> + + + Broker + RW + Single + Mandatory + Objlnk + + + + + + Topic + RW + Single + Mandatory + String + + + + + + QoS + RW + Single + Mandatory + Unsigned Integer + 0..2 + + + + + Retain + RW + Single + Mandatory + Boolean + + + + + + Active + RW + Single + Optional + Boolean + + + + + + Encoding + RW + Single + Optional + Unsigned Integer + 0..65535 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/19.xml b/application/src/main/data/lwm2m-registry/19.xml new file mode 100644 index 0000000000..c4f5cbe6ab --- /dev/null +++ b/application/src/main/data/lwm2m-registry/19.xml @@ -0,0 +1,144 @@ + + + + + BinaryAppDataContainer + + 19 + urn:oma:lwm2m:oma:19 + 1.0 + 1.0 + Multiple + Optional + + Data + RW + Multiple + Mandatory + Opaque + + + + + Data Priority + RW + Single + Optional + Integer + 1 bytes + + + + Data Creation Time + RW + Single + Optional + Time + + + + + Data Description + RW + Single + Optional + String + 32 bytes + + + + Data Format + RW + Single + Optional + String + 32 bytes + + + + App ID + RW + Single + Optional + Integer + 2 bytes + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2.xml b/application/src/main/data/lwm2m-registry/2.xml new file mode 100644 index 0000000000..4ea5805b36 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2.xml @@ -0,0 +1,123 @@ + + + + + + + LwM2M Access Control + + 2 + urn:oma:lwm2m:oma:2:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Object ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Object Instance ID + R + Single + Mandatory + Integer + 0..65535 + + + + + ACL + RW + Multiple + Optional + Integer + 0..31 + + + + + Access Control Owner + RW + Single + Mandatory + Integer + 0..65535 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/20.xml b/application/src/main/data/lwm2m-registry/20.xml new file mode 100644 index 0000000000..68aa96048c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/20.xml @@ -0,0 +1,142 @@ + + + + + + + + Event Log + + 20 + urn:oma:lwm2m:oma:20:2.0 + 1.0 + 2.0 + Multiple + Optional + + LogClass + RW + Single + Optional + Integer + 255 + + + + LogStart + E + Single + Optional + + + + + + LogStop + E + Single + Optional + + + + + + LogStatus + R + Single + Optional + Integer + 8-Bits + + + + LogData + R + Single + Mandatory + Opaque + + + + + LogDataFormat + RW + Single + Optional + Integer + 255 + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2048.xml b/application/src/main/data/lwm2m-registry/2048.xml new file mode 100644 index 0000000000..0bc99cffd1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2048.xml @@ -0,0 +1,90 @@ + + + + + + + CmdhPolicy + + 2048 + urn:oma:lwm2m:ext:20481.0 + 1.0Multiple + Optional + + Name + RW + Single + Mandatory + String + + + + + DefaultRule + RW + Single + Mandatory + Objlnk + + + + + LimiRules + RW + Multiple + Mandatory + Objlnk + + + + + NetworkAccessECRules + RW + Multiple + Mandatory + Objlnk + + + + + BufferRules + RW + Multiple + Mandatory + Objlnk + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2049.xml b/application/src/main/data/lwm2m-registry/2049.xml new file mode 100644 index 0000000000..7509b22160 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2049.xml @@ -0,0 +1,59 @@ + + + + + + + ActiveCmdhPolicy + + 2049 + urn:oma:lwm2m:ext:20491.0 + 1.0Single + Optional + + ActiveLink + RW + Single + Mandatory + Objlnk + + + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2050.xml b/application/src/main/data/lwm2m-registry/2050.xml new file mode 100644 index 0000000000..da27a90de0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2050.xml @@ -0,0 +1,64 @@ + + + + + + + CmdhDefaults + + 2050 + urn:oma:lwm2m:ext:20501.0 + 1.0Multiple + Optional + + DefaultEcRules + RW + Multiple + Mandatory + Objlnk + + + + + + DefaultEcParamRules + RW + Multiple + Mandatory + Objlnk + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2051.xml b/application/src/main/data/lwm2m-registry/2051.xml new file mode 100644 index 0000000000..1f4ecda5d1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2051.xml @@ -0,0 +1,99 @@ + + + + + + + CmdhDefEcValues + + 2051 + urn:oma:lwm2m:ext:20511.0 + 1.0Multiple + Optional + + Order + RW + Single + Mandatory + Integer + + + + + DefEcValue + RW + Single + Mandatory + String + + + + + RequestOrigin + RW + Multiple + Mandatory + String + + + + + RequestContext + RW + Single + Optional + String + + + + + RequestContextNotification + RW + Single + Optional + Boolean + + + + + RequestCharacteristics + RW + Single + Optional + String + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2052.xml b/application/src/main/data/lwm2m-registry/2052.xml new file mode 100644 index 0000000000..abaae1b54e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2052.xml @@ -0,0 +1,111 @@ + + + + + + + CmdhEcDefParamValues + + 2052 + urn:oma:lwm2m:ext:20521.0 + 1.0Multiple + Optional + + ApplicableEventCategory + RW + Multiple + Mandatory + Integer + + + + + DefaultRequestExpTime + RW + Single + Mandatory + Integer + + ms + + + + + + + + + DefaultResultExpTime + RW + Single + Mandatory + Integer + + ms + + + + DefaultOpExecTime + RW + Single + Mandatory + Integer + + ms + + + DefaultRespPersistence + RW + Single + Mandatory + Integer + + ms + + + DefaultDelAggregation + RW + Single + Mandatory + Integer + + ms + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2053.xml b/application/src/main/data/lwm2m-registry/2053.xml new file mode 100644 index 0000000000..a765a7894a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2053.xml @@ -0,0 +1,165 @@ + + + + + + + CmdhLimits + + 2053 + urn:oma:lwm2m:ext:20531.0 + 1.0Multiple + Optional + + Order + RW + Single + Mandatory + Integer + + + + + RequestOrigin + RW + Multiple + Mandatory + String + + + + + + + + + + + RequestContext + RW + Single + Optional + String + + + + + + RequestContextNotificatio + RW + Single + Optional + Boolean + + + + + RequestCharacteristics + RW + Single + Optional + String + + + + + LimitsEventCategory + RW + Multiple + Mandatory + Integer + + + + + LimitsRequestExpTime + RW + Multiple + Mandatory + Integer + 2 Instances + ms + + + LimitsResultExpTime + RW + Multiple + Mandatory + Integer + 2 Instances + ms + + + LimitsOptExpTime + RW + Multiple + Mandatory + Integer + 2 Instances + ms + + + LimitsRespPersistence + RW + Multiple + Mandatory + Integer + 2 Instances + ms + + + LimitsDelAggregation + RW + Multiple + Mandatory + String + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2054.xml b/application/src/main/data/lwm2m-registry/2054.xml new file mode 100644 index 0000000000..3935fb7253 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2054.xml @@ -0,0 +1,69 @@ + + + + + + + CmdhNetworkAccessRules + + 2054 + urn:oma:lwm2m:ext:20541.0 + 1.0Multiple + Optional + + ApplicableEventCategories + RW + Multiple + Mandatory + Integer + + + + + NetworkAccessRule + RW + Multiple + Optional + Objlnk + + + + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2055.xml b/application/src/main/data/lwm2m-registry/2055.xml new file mode 100644 index 0000000000..cbd0dc5796 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2055.xml @@ -0,0 +1,100 @@ + + + + + + + CmdhNwAccessRule + + 2055 + urn:oma:lwm2m:ext:20551.0 + 1.0Multiple + Optional + + TargetNetwork + RW + Multiple + Mandatory + String + + + + + SpreadingWaitTime + RW + Single + Mandatory + Integer + + ms + + + MinReqVolume + RW + Single + Mandatory + Integer + + B + + + BackOffParameters + RW + Single + Mandatory + Objlnk + + + + + OtherConditions + RW + Single + Mandatory + String + + + + + AllowedSchedule + RW + Multiple + Mandatory + String + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2056.xml b/application/src/main/data/lwm2m-registry/2056.xml new file mode 100644 index 0000000000..ddd50e269a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2056.xml @@ -0,0 +1,77 @@ + + + + + + + CmdhBuffer + + 2056 + urn:oma:lwm2m:ext:20561.0 + 1.0Multiple + Optional + + ApplicableEventCategory + RW + Multiple + Mandatory + Integer + + + + + MaxBufferSize + RW + Single + Mandatory + Integer + + B + + + StoragePriority + RW + Single + Mandatory + Integer + 1..10 + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/2057.xml b/application/src/main/data/lwm2m-registry/2057.xml new file mode 100644 index 0000000000..68d4bcd135 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/2057.xml @@ -0,0 +1,92 @@ + + + + + + + CmdhBackOffParametersSet + + 2057 + urn:oma:lwm2m:ext:2057 + 1.0 + 1.0 + Multiple + Optional + + NetworkAction + RW + Single + Optional + Integer + 1..5 + + + + InitialBackoffTime + RW + Single + Mandatory + Integer + + ms + + + AdditionalBackoffTime + RW + Single + Mandatory + Integer + + ms + + + MaximumBackoffTime + RW + Single + Mandatory + Integer + + ms + + + OptionalRandomBackoffTime + RW + Multiple + Optional + Integer + + ms + + + + + diff --git a/application/src/main/data/lwm2m-registry/21.xml b/application/src/main/data/lwm2m-registry/21.xml new file mode 100644 index 0000000000..5685648bad --- /dev/null +++ b/application/src/main/data/lwm2m-registry/21.xml @@ -0,0 +1,132 @@ + + + + + + + LWM2M OSCORE + + 21 + urn:oma:lwm2m:oma:21:2.0 + 1.1 + 2.0 + Multiple + Optional + + OSCORE Master Secret + + Single + Mandatory + Opaque + + + + + OSCORE Sender ID + + Single + Mandatory + Opaque + + + + + OSCORE Recipient ID + + Single + Mandatory + Opaque + + + + + OSCORE AEAD Algorithm + + Single + Optional + Integer + + + + + OSCORE HMAC Algorithm + + Single + Optional + Integer + + + + + OSCORE Master Salt + + Single + Optional + Opaque + + + + + OSCORE ID Context + + Single + Optional + Opaque + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/22.xml b/application/src/main/data/lwm2m-registry/22.xml new file mode 100644 index 0000000000..02fed409ae --- /dev/null +++ b/application/src/main/data/lwm2m-registry/22.xml @@ -0,0 +1,157 @@ + + + + + + + Virtual Observe Notify + + 22 + urn:oma:lwm2m:oma:22:1.1 + 1.1 + 1.1 + Multiple + Optional + + + ObserveLinks + RW + Multiple + Mandatory + Corelnk + + + + + + Report + R + Single + Mandatory + String + + + + + + ResourceFilter + RW + Single + Optional + Boolean + + + + + + ReportLinks + RW + Multiple + Optional + Corelnk + + + + + + ObserveRelation + RW + Single + Optional + Integer + + + + + + Virtual Observe Report Format + RW + Single + Optional + Integer + + + + + + Virtual Observe Report + R + Single + Optional + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/23.xml b/application/src/main/data/lwm2m-registry/23.xml new file mode 100644 index 0000000000..55db80cc77 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/23.xml @@ -0,0 +1,100 @@ + + + + + + + LwM2M COSE + + 23 + urn:oma:lwm2m:oma:23 + 1.2 + 1.0 + Multiple + Optional + + KID + + Single + Mandatory + String + + + + + AEAD Algorithm + + Single + Mandatory + Integer + + + + + Key + + Single + Mandatory + String + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/24.xml b/application/src/main/data/lwm2m-registry/24.xml new file mode 100644 index 0000000000..7dd6942646 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/24.xml @@ -0,0 +1,163 @@ + + + + + + MQTT Server + + 24 + urn:oma:lwm2m:oma:24 + 1.2 + 1.0 + Multiple + Optional + + + Will Retain + RW + Single + Optional + Boolean + + + + + + Will Topic + RW + Single + Optional + String + + + + + + Will Message + RW + Single + Optional + String + + + + + + Clean Session + RW + Single + Optional + Boolean + + + + + + Will QoS + RW + Single + Optional + Unsigned Integer + 0..3 + + + + + Keep Alive + RW + Single + Optional + Unsigned Integer + 0..65535 + + + + + Client Identifier + RW + Single + Mandatory + String + + + + + + User Name + RW + Single + Optional + String + + + + + + Password + RW + Single + Optional + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/25.xml b/application/src/main/data/lwm2m-registry/25.xml new file mode 100644 index 0000000000..5ab62b3c36 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/25.xml @@ -0,0 +1,104 @@ + + + + + + LwM2M Gateway + + 25 + urn:oma:lwm2m:oma:25:2.0 + 1.1 + 2.0 + Multiple + Optional + + + Device ID + R + Single + Mandatory + String + + + + + + Prefix + R + Single + Mandatory + String + + + + + + IoT Device Objects + R + Single + Mandatory + Corelnk + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/26.xml b/application/src/main/data/lwm2m-registry/26.xml new file mode 100644 index 0000000000..0f77aad973 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/26.xml @@ -0,0 +1,93 @@ + + + + + + LwM2M Gateway Routing + + 26 + urn:oma:lwm2m:oma:26 + 1.2 + 1.0 + Multiple + Optional + + + LwM2M Object ID + R + Single + Mandatory + Unsigned Integer + + + + + + Mapping Info + R + Single + Mandatory + Corelnk + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/27.xml b/application/src/main/data/lwm2m-registry/27.xml new file mode 100644 index 0000000000..9f30947997 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/27.xml @@ -0,0 +1,322 @@ + + + + + + 5GNR Connectivity + + 27 + urn:oma:lwm2m:oma:27 + 1.2 + 1.0 + Multiple + Optional + + + Connectivity Option + R + Single + Mandatory + Integer + + + + + + NR Band Support available + R + Multiple + Optional + Integer + + + + + + NR Band attached + R + Multiple + Optional + Integer + + + + + + S-NSSAI + R + Multiple + Optional + Integer + + + + + + DNN Name + R + Single + Optional + String + + + + + + PDU Session Id + R + Single + Optional + Integer + 1...15 + + + + + SSC Mode + R + Single + Optional + Integer + 1...3 + + + + + PDU Session Type + R + Single + Optional + Integer + + + + + + 5QI + R + Single + Optional + Integer + + + + + + SDAP Enablement + R + Single + Optional + Integer + + + + + + QFI + R + Single + Optional + Integer + 1...63 + + + + + Session AMBR + R + Single + Optional + Integer + 1...25 + + + + + APN-AMBR + R + Single + Optional + Integer + + dB + + + + NAS Reflective QOS + R + Single + Optional + Boolean + + + + + + Access Stratum Reflective QoS + R + Single + Optional + Boolean + + + + + + P-CSCF Address Index + R + Single + Optional + Integer + + + + + + PDU session Authentication + R + Single + Optional + Integer + + + + + + PLMN ID + R + Single + Optional + String + + + + + + LADN Support + R + Single + Optional + Boolean + + + + + + Integrity Protection on DRB + R + Single + Optional + Integer + + + + + + Access type Preference + R + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/28.xml b/application/src/main/data/lwm2m-registry/28.xml new file mode 100644 index 0000000000..8edd51bcc5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/28.xml @@ -0,0 +1,150 @@ + + + + + + + Device RF Capabilities + + 28 + urn:oma:lwm2m:oma:28 + 1.1 + 1.0 + Single + Optional + + + Supported LTE Band Information + R + Single + Optional + Integer + + + + + + Supported EGPRS Band Information + R + Single + Optional + Integer + + + + + + Supported TD-SCDMA Band Information + R + Single + Optional + Integer + + + + + + Supported WCDMA Band Information + R + Single + Optional + Integer + + + + + + Supported CDMA-2000 Band Information + R + Single + Optional + Integer + + + + + + Supported WiMAX Band Information + R + Single + Optional + Integer + + + + + + Supported NB-IoT Band Information + R + Single + Optional + Integer + + + + + + Supported 5G Band Information + R + Single + Optional + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3.xml b/application/src/main/data/lwm2m-registry/3.xml new file mode 100644 index 0000000000..cb360acd10 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3.xml @@ -0,0 +1,331 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3:1.2 + 1.1 + 1.2 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + /100 + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..32 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3200.xml b/application/src/main/data/lwm2m-registry/3200.xml new file mode 100644 index 0000000000..6a6310b5bb --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3200.xml @@ -0,0 +1,148 @@ + + + + + + + Digital Input + Generic digital input for non-specific sensors + 3200 + urn:oma:lwm2m:ext:3200:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Digital Input State + R + Single + Mandatory + Boolean + + + The current state of a digital input. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + Digital Input Polarity + RW + Single + Optional + Boolean + + + The polarity of the digital input as a Boolean (False = Normal, True = Reversed). + + + Digital Input Debounce + RW + Single + Optional + Integer + + ms + The debounce period in ms. + + + Digital Input Edge Selection + RW + Single + Optional + Integer + 1..3 + + The edge selection as an integer (1 = Falling edge, 2 = Rising edge, 3 = Both Rising and Falling edge). + + + Digital Input Counter Reset + E + Single + Optional + + + + Reset the Counter value. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Sensor Type + R + Single + Optional + String + + + The type of the sensor (for instance PIR type). + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3201.xml b/application/src/main/data/lwm2m-registry/3201.xml new file mode 100644 index 0000000000..b950077387 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3201.xml @@ -0,0 +1,98 @@ + + + + + + + Digital Output + Generic digital output for non-specific actuators + 3201 + urn:oma:lwm2m:ext:3201:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Digital Output State + RW + Single + Mandatory + Boolean + + + The current state of a digital output. + + + Digital Output Polarity + RW + Single + Optional + Boolean + + + The polarity of the digital output as a Boolean (False = Normal, True = Reversed). + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3202.xml b/application/src/main/data/lwm2m-registry/3202.xml new file mode 100644 index 0000000000..7814f6ccf9 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3202.xml @@ -0,0 +1,168 @@ + + + + + + + Analog Input + Generic analog input for non-specific sensors + 3202 + urn:oma:lwm2m:ext:3202:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Analog Input Current Value + R + Single + Mandatory + Float + + + The current value of the analog input. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Sensor Type + R + Single + Optional + String + + + The type of the sensor (for instance PIR type). + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3203.xml b/application/src/main/data/lwm2m-registry/3203.xml new file mode 100644 index 0000000000..0316e920a5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3203.xml @@ -0,0 +1,108 @@ + + + + + + + Analog Output + This IPSO object is a generic object that can be used with any kind of analog output interface. + 3203 + urn:oma:lwm2m:ext:3203:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Analog Output Current Value + RW + Single + Mandatory + Float + 0..1 + + The current value of the analog output. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3300.xml b/application/src/main/data/lwm2m-registry/3300.xml new file mode 100644 index 0000000000..f7a5b8a3e2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3300.xml @@ -0,0 +1,178 @@ + + + + + + + Generic Sensor + This IPSO object allows the description of a generic sensor. It is based on the description of a value and a unit according to the SenML specification. Thus, any type of value defined within this specification can be reported using this object. This object may be used as a generic object if a dedicated one does not exist. + 3300 + urn:oma:lwm2m:ext:3300:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Sensor Type + R + Single + Optional + String + + + The type of the sensor (for instance PIR type). + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3301.xml b/application/src/main/data/lwm2m-registry/3301.xml new file mode 100644 index 0000000000..6604464f20 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3301.xml @@ -0,0 +1,168 @@ + + + + + + + Illuminance + Illuminance sensor, example units = lx + 3301 + urn:oma:lwm2m:ext:3301:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3302.xml b/application/src/main/data/lwm2m-registry/3302.xml new file mode 100644 index 0000000000..fe9e886408 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3302.xml @@ -0,0 +1,158 @@ + + + + + + + Presence + Presence sensor with digital sensing, optional delay parameters + 3302 + urn:oma:lwm2m:ext:3302:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Digital Input State + R + Single + Mandatory + Boolean + + + The current state of a digital input. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + Digital Input Counter Reset + E + Single + Optional + + + + Reset the Counter value. + + + Sensor Type + R + Single + Optional + String + + + The type of the sensor (for instance PIR type). + + + Busy to Clear delay + RW + Single + Optional + Integer + + ms + Delay from the detection state to the clear state in ms. + + + Clear to Busy delay + RW + Single + Optional + Integer + + ms + Delay from the clear state to the busy state in ms. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3303.xml b/application/src/main/data/lwm2m-registry/3303.xml new file mode 100644 index 0000000000..2db5da74c8 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3303.xml @@ -0,0 +1,168 @@ + + + + + + + Temperature + This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius. + 3303 + urn:oma:lwm2m:ext:3303:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3304.xml b/application/src/main/data/lwm2m-registry/3304.xml new file mode 100644 index 0000000000..7331c5f83a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3304.xml @@ -0,0 +1,168 @@ + + + + + + + Humidity + This IPSO object should be used with a humidity sensor to report a humidity measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the humidity sensor. An example measurement unit is relative humidity as a percentage. + 3304 + urn:oma:lwm2m:ext:3304:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3305.xml b/application/src/main/data/lwm2m-registry/3305.xml new file mode 100644 index 0000000000..f927a44a59 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3305.xml @@ -0,0 +1,278 @@ + + + + + + + Power Measurement + This IPSO object should be used over a power measurement sensor to report a remote power measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range for both active and reactive power. It also provides resources for cumulative energy, calibration, and the power factor. + 3305 + urn:oma:lwm2m:ext:3305:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Instantaneous active power + R + Single + Mandatory + Float + + W + The current active power. + + + Min Measured active power + R + Single + Optional + Float + + W + The minimum active power measured by the sensor since it is ON. + + + Max Measured active power + R + Single + Optional + Float + + W + The maximum active power measured by the sensor since it is ON. + + + Min Range active power + R + Single + Optional + Float + + W + The minimum active power that can be measured by the sensor. + + + Max Range active power + R + Single + Optional + Float + + W + The maximum active power that can be measured by the sensor. + + + Cumulative active power + R + Single + Optional + Float + + Wh + The cumulative active power since the last cumulative energy reset or device start. + + + Active Power Calibration + W + Single + Optional + Float + + W + Request an active power calibration by writing the value of a calibrated load. + + + Instantaneous reactive power + R + Single + Optional + Float + + var + The current reactive power. + + + Min Measured reactive power + R + Single + Optional + Float + + var + The minimum reactive power measured by the sensor since it is ON. + + + Max Measured reactive power + R + Single + Optional + Float + + var + The maximum reactive power measured by the sensor since it is ON. + + + Min Range reactive power + R + Single + Optional + Float + + var + The minimum active power that can be measured by the sensor. + + + Max Range reactive power + R + Single + Optional + Float + + var + The maximum reactive power that can be measured by the sensor. + + + Cumulative reactive power + R + Single + Optional + Float + + varh + The cumulative reactive power since the last cumulative energy reset or device start. + + + Reactive Power Calibration + W + Single + Optional + Float + + var + Request a reactive power calibration by writing the value of a calibrated load. + + + Power factor + R + Single + Optional + Float + + + If applicable, the power factor of the current consumption. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Reset Cumulative energy + E + Single + Optional + + + + Reset both cumulative active/reactive power. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3306.xml b/application/src/main/data/lwm2m-registry/3306.xml new file mode 100644 index 0000000000..748bc0f005 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3306.xml @@ -0,0 +1,118 @@ + + + + + + + Actuation + This IPSO object is dedicated to remote actuation such as ON/OFF action or dimming. A multi-state output can also be described as a string. This is useful to send pilot wire orders for instance. It also provides a resource to reflect the time that the device has been switched on. + 3306 + urn:oma:lwm2m:ext:3306:1.1 + 1.0 + 1.1 + Multiple + Optional + + + On/Off + RW + Single + Mandatory + Boolean + + + On/off control. Boolean value where True is On and False is Off. + + + Dimmer + RW + Single + Optional + Integer + 0..100 + /100 + This resource represents a dimmer setting, which has an Integer value between 0 and 100 as a percentage. + + + On time + RW + Single + Optional + Integer + + s + The time in seconds that the device has been on. Writing a value of 0 resets the counter. + + + Multi-state Output + RW + Single + Optional + String + + + A string describing a state for multiple level output such as Pilot Wire. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3308.xml b/application/src/main/data/lwm2m-registry/3308.xml new file mode 100644 index 0000000000..f1d5c18f12 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3308.xml @@ -0,0 +1,108 @@ + + + + + + + Set Point + This IPSO object should be used to set a desired value to a controller, such as a thermostat. A special resource is added to set the colour of an object. + 3308 + urn:oma:lwm2m:ext:3308:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Set Point Value + RW + Single + Mandatory + Float + + + The setpoint value. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Colour + RW + Single + Optional + String + + + A string representing a value in some color space. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3310.xml b/application/src/main/data/lwm2m-registry/3310.xml new file mode 100644 index 0000000000..6b4e04d6b6 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3310.xml @@ -0,0 +1,138 @@ + + + + + + + Load Control + This Object is used for demand-response load control and other load control in automation application (not limited to power). + 3310 + urn:oma:lwm2m:ext:3310:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Event Identifier + RW + Single + Mandatory + String + + + The event identifier as a string. + + + Start Time + RW + Single + Mandatory + Time + + + Time when the event started. + + + Duration In Min + RW + Single + Mandatory + Integer + + min + The duration of the event in minutes. + + + Criticality Level + RW + Single + Optional + Integer + 0..3 + + The criticality of the event. The device receiving the event will react in an appropriate fashion for the device. + + + Avg Load AdjPct + RW + Single + Optional + Integer + 0..100 + /100 + Defines the maximum energy usage of the receiving device, as a percentage of the device's normal maximum energy usage. + + + Duty Cycle + RW + Single + Optional + Integer + 0..100 + /100 + Defines the duty cycle for the load control event, i.e, what percentage of time the receiving device is allowed to be on. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3311.xml b/application/src/main/data/lwm2m-registry/3311.xml new file mode 100644 index 0000000000..4c98be2522 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3311.xml @@ -0,0 +1,127 @@ + + + + + + Light Control + This Object is used to control a light source, such as a LED or other light. It allows a light to be turned on or off and its dimmer setting to be control as a % between 0 and 100. An optional colour setting enables a string to be used to indicate the desired colour. + 3311 + urn:oma:lwm2m:ext:3311 + 1.0 + 1.0 + Multiple + Optional + + + On/Off + RW + Single + Mandatory + Boolean + + + On/off control. Boolean value where True is On and False is Off. + + + Dimmer + RW + Single + Optional + Integer + 0..100 + /100 + This resource represents a dimmer setting, which has an Integer value between 0 and 100 as a percentage. + + + On time + RW + Single + Optional + Integer + + s + The time in seconds that the device has been on. Writing a value of 0 resets the counter. + + + Cumulative active power + R + Single + Optional + Float + + Wh + The cumulative active power since the last cumulative energy reset or device start. + + + Power factor + R + Single + Optional + Float + + + If applicable, the power factor of the current consumption. + + + Colour + RW + Single + Optional + String + + + A string representing a value in some color space. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3312.xml b/application/src/main/data/lwm2m-registry/3312.xml new file mode 100644 index 0000000000..17bd0312db --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3312.xml @@ -0,0 +1,127 @@ + + + + + + Power Control + This Object is used to control a power source, such as a Smart Plug. It allows a power relay to be turned on or off and its dimmer setting to be control as a % between 0 and 100. + 3312 + urn:oma:lwm2m:ext:3312:1.1 + 1.0 + 1.1 + Multiple + Optional + + + On/Off + RW + Single + Mandatory + Boolean + + + On/off control. Boolean value where True is On and False is Off. + + + Dimmer + RW + Single + Optional + Integer + 0..100 + /100 + This resource represents a dimmer setting, which has an Integer value between 0 and 100 as a percentage. + + + On time + RW + Single + Optional + Integer + + s + The time in seconds that the device has been on. Writing a value of 0 resets the counter. + + + Cumulative active power + R + Single + Optional + Float + + Wh + The cumulative active power since the last cumulative energy reset or device start. + + + Power factor + R + Single + Optional + Float + + + If applicable, the power factor of the current consumption. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3313.xml b/application/src/main/data/lwm2m-registry/3313.xml new file mode 100644 index 0000000000..43d0d63b88 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3313.xml @@ -0,0 +1,157 @@ + + + + + + Accelerometer + This IPSO object can be used to represent a 1-3 axis accelerometer. + 3313 + urn:oma:lwm2m:ext:3313:1.1 + 1.0 + 1.1 + Multiple + Optional + + + X Value + R + Single + Mandatory + Float + + + The measured value along the X axis. + + + Y Value + R + Single + Optional + Float + + + The measured value along the Y axis. + + + Z Value + R + Single + Optional + Float + + + The measured value along the Z axis. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3314.xml b/application/src/main/data/lwm2m-registry/3314.xml new file mode 100644 index 0000000000..96ebc5a537 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3314.xml @@ -0,0 +1,147 @@ + + + + + + Magnetometer + This IPSO object can be used to represent a 1-3 axis magnetometer with optional compass direction. + 3314 + urn:oma:lwm2m:ext:3314:1.1 + 1.0 + 1.1 + Multiple + Optional + + + X Value + R + Single + Mandatory + Float + + + The measured value along the X axis. + + + Y Value + R + Single + Optional + Float + + + The measured value along the Y axis. + + + Z Value + R + Single + Optional + Float + + + The measured value along the Z axis. + + + Compass Direction + R + Single + Optional + Float + 0..360 + deg + The measured compass direction. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3315.xml b/application/src/main/data/lwm2m-registry/3315.xml new file mode 100644 index 0000000000..d54a09edd8 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3315.xml @@ -0,0 +1,168 @@ + + + + + + + Barometer + This IPSO object should be used with an air pressure sensor to report a barometer measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the barometer sensor. An example measurement unit is pascals. + 3315 + urn:oma:lwm2m:ext:3315:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3316.xml b/application/src/main/data/lwm2m-registry/3316.xml new file mode 100644 index 0000000000..16a20a219a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3316.xml @@ -0,0 +1,179 @@ + + + + + + + Voltage + This IPSO object should be used with voltmeter sensor to report measured voltage between two points. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is volts. + + 3316 + urn:oma:lwm2m:ext:3316:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3317.xml b/application/src/main/data/lwm2m-registry/3317.xml new file mode 100644 index 0000000000..9d41da70de --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3317.xml @@ -0,0 +1,179 @@ + + + + + + + Current + This IPSO object should be used with an ammeter to report measured electric current in amperes. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is ampere. + + 3317 + urn:oma:lwm2m:ext:3317:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3318.xml b/application/src/main/data/lwm2m-registry/3318.xml new file mode 100644 index 0000000000..957b593c89 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3318.xml @@ -0,0 +1,179 @@ + + + + + + + Frequency + This IPSO object should be used to report frequency measurements. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is hertz. + + 3318 + urn:oma:lwm2m:ext:3318:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3319.xml b/application/src/main/data/lwm2m-registry/3319.xml new file mode 100644 index 0000000000..e3a13ea3ef --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3319.xml @@ -0,0 +1,179 @@ + + + + + + + Depth + This IPSO object should be used to report depth measurements. It can, for example, be used to describe a generic rain gauge that measures the accumulated rainfall in millimetres (mm). + + 3319 + urn:oma:lwm2m:ext:3319:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3320.xml b/application/src/main/data/lwm2m-registry/3320.xml new file mode 100644 index 0000000000..9ec16fb671 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3320.xml @@ -0,0 +1,179 @@ + + + + + + + Percentage + This IPSO object should can be used to report measurements relative to a 0-100% scale. For example it could be used to measure the level of a liquid in a vessel or container in units of %. + + 3320 + urn:oma:lwm2m:ext:3320:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3321.xml b/application/src/main/data/lwm2m-registry/3321.xml new file mode 100644 index 0000000000..e4a1f80bf1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3321.xml @@ -0,0 +1,179 @@ + + + + + + + Altitude + This IPSO object should be used with an altitude sensor to report altitude above sea level in meters. Note that Altitude can be calculated from the measured pressure given the local sea level pressure. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is meters. + + 3321 + urn:oma:lwm2m:ext:3321:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3322.xml b/application/src/main/data/lwm2m-registry/3322.xml new file mode 100644 index 0000000000..838ed44995 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3322.xml @@ -0,0 +1,179 @@ + + + + + + + Load + This IPSO object should be used with a load sensor (as in a scale) to report the applied weight or force. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is kilograms. + + 3322 + urn:oma:lwm2m:ext:3322:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3323.xml b/application/src/main/data/lwm2m-registry/3323.xml new file mode 100644 index 0000000000..e65c11c123 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3323.xml @@ -0,0 +1,179 @@ + + + + + + + Pressure + This IPSO object should be used to report pressure measurements. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is pascals. + + 3323 + urn:oma:lwm2m:ext:3323:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3324.xml b/application/src/main/data/lwm2m-registry/3324.xml new file mode 100644 index 0000000000..b80d427b91 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3324.xml @@ -0,0 +1,179 @@ + + + + + + + Loudness + This IPSO object should be used to report loudness or noise level measurements. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is decibels. + + 3324 + urn:oma:lwm2m:ext:3324:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3325.xml b/application/src/main/data/lwm2m-registry/3325.xml new file mode 100644 index 0000000000..c0577d64cd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3325.xml @@ -0,0 +1,179 @@ + + + + + + + Concentration + This IPSO object should be used to the particle concentration measurement of a medium. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is parts per million. + + 3325 + urn:oma:lwm2m:ext:3325:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3326.xml b/application/src/main/data/lwm2m-registry/3326.xml new file mode 100644 index 0000000000..e247637bcf --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3326.xml @@ -0,0 +1,179 @@ + + + + + + + Acidity + This IPSO object should be used to report an acidity measurement of a liquid. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is pH. + + 3326 + urn:oma:lwm2m:ext:3326:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3327.xml b/application/src/main/data/lwm2m-registry/3327.xml new file mode 100644 index 0000000000..9cc689459f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3327.xml @@ -0,0 +1,179 @@ + + + + + + + Conductivity + This IPSO object should be used to report a measurement of the electric conductivity of a medium or sample. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is Siemens. + + 3327 + urn:oma:lwm2m:ext:3327:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3328.xml b/application/src/main/data/lwm2m-registry/3328.xml new file mode 100644 index 0000000000..752ebe0c53 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3328.xml @@ -0,0 +1,179 @@ + + + + + + + Power + This IPSO object should be used to report power measurements. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is Watts. This object may be used for either real power or apparent power measurements. + + 3328 + urn:oma:lwm2m:ext:3328:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3329.xml b/application/src/main/data/lwm2m-registry/3329.xml new file mode 100644 index 0000000000..fe221a1564 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3329.xml @@ -0,0 +1,179 @@ + + + + + + + Power Factor + This IPSO object should be used to report a measurement or calculation of the power factor of a reactive electrical load. Power Factor is normally the ratio of non-reactive power to total power. This object also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. + + 3329 + urn:oma:lwm2m:ext:3329:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3330.xml b/application/src/main/data/lwm2m-registry/3330.xml new file mode 100644 index 0000000000..baec2dab14 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3330.xml @@ -0,0 +1,179 @@ + + + + + + + Distance + This IPSO object should be used to report a distance measurement. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is Meters. + + 3330 + urn:oma:lwm2m:ext:3330:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3331.xml b/application/src/main/data/lwm2m-registry/3331.xml new file mode 100644 index 0000000000..1094d5bf31 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3331.xml @@ -0,0 +1,129 @@ + + + + + + + Energy + This IPSO object should be used to report energy consumption (Cumulative Power) of an electrical load. An example measurement unit is Watt Hours. + + 3331 + urn:oma:lwm2m:ext:3331:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Cumulative energy + E + Single + Optional + + + + Reset both cumulative active/reactive power. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3332.xml b/application/src/main/data/lwm2m-registry/3332.xml new file mode 100644 index 0000000000..d4026e9558 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3332.xml @@ -0,0 +1,139 @@ + + + + + + + Direction + This IPSO object is used to report the direction indicated by a compass, wind vane, or other directional indicator. The units of measure is plane angle degrees. + + 3332 + urn:oma:lwm2m:ext:3332:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Compass Direction + R + Single + Mandatory + Float + 0..360 + deg + The measured compass direction. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3333.xml b/application/src/main/data/lwm2m-registry/3333.xml new file mode 100644 index 0000000000..1fc7117482 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3333.xml @@ -0,0 +1,98 @@ + + + + + + Time + This IPSO object is used to report the current time in seconds since January 1, 1970 UTC. There is also a fractional time counter that has a range of less than one second. + + 3333 + urn:oma:lwm2m:ext:3333:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Current Time + RW + Single + Mandatory + Time + + + Unix Time. A signed integer representing the number of seconds since Jan 1st, 1970 in the UTC time zone. + + + Fractional Time + RW + Single + Optional + Float + 0..1 + s + Fractional part of the time when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3334.xml b/application/src/main/data/lwm2m-registry/3334.xml new file mode 100644 index 0000000000..4ff3e6c6a6 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3334.xml @@ -0,0 +1,229 @@ + + + + + + + Gyrometer + This IPSO Object is used to report the current reading of a gyrometer sensor in 3 axes. It provides tracking of the minimum and maximum angular rate in all 3 axes. An example unit of measure is radians per second. + + 3334 + urn:oma:lwm2m:ext:3334:1.1 + 1.0 + 1.1 + Multiple + Optional + + + X Value + R + Single + Mandatory + Float + + + The measured value along the X axis. + + + Y Value + R + Single + Optional + Float + + + The measured value along the Y axis. + + + Z Value + R + Single + Optional + Float + + + The measured value along the Z axis. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min X Value + R + Single + Optional + Float + + + The minimum measured value along the X axis. + + + Max X Value + R + Single + Optional + Float + + + The maximum measured value along the X axis. + + + Min Y Value + R + Single + Optional + Float + + + The minimum measured value along the Y axis. + + + Max Y Value + R + Single + Optional + Float + + + The maximum measured value along the Y axis. + + + Min Z Value + R + Single + Optional + Float + + + The minimum measured value along the Z axis. + + + Max Z Value + R + Single + Optional + Float + + + The maximum measured value along the Z axis. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3335.xml b/application/src/main/data/lwm2m-registry/3335.xml new file mode 100644 index 0000000000..9f608653f5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3335.xml @@ -0,0 +1,119 @@ + + + + + + + Colour + This IPSO object should be used to report the measured value of a colour sensor in some colour space described by the units resource. + + 3335 + urn:oma:lwm2m:ext:3335:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Colour + RW + Single + Mandatory + String + + + A string representing a value in some color space. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3336.xml b/application/src/main/data/lwm2m-registry/3336.xml new file mode 100644 index 0000000000..68fea3b90e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3336.xml @@ -0,0 +1,149 @@ + + + + + + + Location + This IPSO object represents GPS coordinates. This object is compatible with the LwM2M management object for location, but uses reusable resources. + + 3336 + urn:oma:lwm2m:ext:3336:2.0 + 1.0 + 2.0 + Multiple + Optional + + + Numeric Latitude + R + Single + Mandatory + Float + + lat + The decimal notation of latitude, e.g. -43.5723 (World Geodetic System 1984). + + + Numeric Longitude + R + Single + Mandatory + Float + + lon + The decimal notation of longitude, e.g. 153.21760 (World Geodetic System 1984). + + + Numeric Uncertainty + R + Single + Optional + Float + + m + The accuracy of the position in meters. + + + Compass Direction + R + Single + Optional + Float + 0..360 + deg + The measured compass direction. + + + Velocity + R + Single + Optional + Opaque + + + The velocity of the device as defined in 3GPP 23.032 GAD specification. This set of values may not be available if the device is static. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3337.xml b/application/src/main/data/lwm2m-registry/3337.xml new file mode 100644 index 0000000000..fa4eefbe81 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3337.xml @@ -0,0 +1,159 @@ + + + + + + + Positioner + This IPSO object should be used with a generic position actuator with range from 0 to 100%. This object optionally allows setting the transition time for an operation that changes the position of the actuator, and for reading the remaining time of the currently active transition. + + 3337 + urn:oma:lwm2m:ext:3337:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Current Position + RW + Single + Mandatory + Float + 0..100 + /100 + Current position or desired position of a positioner actuator. + + + Transition Time + RW + Single + Optional + Float + + s + The time expected to move the actuator to the new position. + + + Remaining Time + R + Single + Optional + Float + + s + The time remaining in an operation. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Min Limit + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Limit + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3338.xml b/application/src/main/data/lwm2m-registry/3338.xml new file mode 100644 index 0000000000..fd1fba3332 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3338.xml @@ -0,0 +1,119 @@ + + + + + + + Buzzer + This IPSO object should be used to actuate an audible alarm such as a buzzer, beeper, or vibration alarm. There is a dimmer control for setting the relative level of the alarm, and an optional duration control to limit the length of time the alarm sounds when turned on. Each time "true" is written to the On/Off resource, the alarm will sound again for the configured duration. If no duration is programmed or the setting is "false", writing a "true" to the On/Off resource will result in the alarm sounding continuously until a "false" is written to the On/Off resource. + + 3338 + urn:oma:lwm2m:ext:3338:1.1 + 1.0 + 1.1 + Multiple + Optional + + + On/Off + RW + Single + Mandatory + Boolean + + + On/off control. Boolean value where True is On and False is Off. + + + Level + RW + Single + Optional + Float + 0..100 + /100 + Used to represent a level control such as audio volume. + + + Delay Duration + RW + Single + Optional + Float + + s + The duration of the time delay. + + + Minimum Off-time + RW + Single + Mandatory + Float + + s + The duration of the rearm delay (i.e. the delay from the end of one cycle until the beginning of the next, the inhibit time). + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3339.xml b/application/src/main/data/lwm2m-registry/3339.xml new file mode 100644 index 0000000000..194685474e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3339.xml @@ -0,0 +1,98 @@ + + + + + + + Audio Clip + This IPSO object should be used for a speaker that plays a pre-recorded audio clip or an audio output that is sent elsewhere. For example, an elevator which announces the floor of the building. A resource is provided to store the clip, a dimmer resource controls the relative sound level of the playback, and a duration resource limits the maximum playback time. After the duration time is reached, any remaining samples in the clip are ignored, and the clip player will be ready to play another clip. + 3339 + urn:oma:lwm2m:ext:3339 + 1.0 + 1.0 + Multiple + Optional + + + Clip + RW + Single + Mandatory + Opaque + + + Audio clip that is playable (e.g., a short audio recording indicating the floor in an elevator). + + + Trigger + E + Single + Optional + + + + Trigger initiating actuation. + + + Level + RW + Single + Optional + Float + 0..100 + /100 + Used to represent a level control such as audio volume. + + + Duration + RW + Single + Optional + Float + + s + The duration of the sound once trigger. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3340.xml b/application/src/main/data/lwm2m-registry/3340.xml new file mode 100644 index 0000000000..35de944f3a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3340.xml @@ -0,0 +1,159 @@ + + + + + + + Timer + This IPSO object is used to time events and actions, using patterns common to industrial timers. A write to the trigger resource or On/Off input state change starts the timing operation, and the timer remaining time shows zero when the operation is complete. The patterns supported are One-Shot (mode 1), On-Time or Interval (mode 2), Time delay on pick-up or TDPU (mode 3), and Time Delay on Drop-Out or TDDO (mode 4). Mode 0 disables the timer, so the output follows the input with no delay. A counter is provided to count occurrences of the timer output changing from 0 to 1. Writing a value of zero resets the counter. The Digital Input State resource reports the state of the timer output. + + 3340 + urn:oma:lwm2m:ext:3340 + 1.0 + 1.0 + Multiple + Optional + + + Delay Duration + RW + Single + Mandatory + Float + + s + The duration of the time delay. + + + Remaining Time + R + Single + Optional + Float + + s + The time remaining in an operation. + + + Minimum Off-time + RW + Single + Optional + Float + + s + The duration of the rearm delay (i.e. the delay from the end of one cycle until the beginning of the next, the inhibit time). + + + Trigger + E + Single + Optional + + + + Trigger initiating actuation. + + + On/Off + RW + Single + Optional + Boolean + + + On/off control. Boolean value where True is On and False is Off. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + Cumulative Time + RW + Single + Optional + Float + + s + The total time in seconds that the timer input is true. Writing a 0 resets the time. + + + Digital State + R + Single + Optional + Boolean + + + The current state of the timer output. + + + Counter + RW + Single + Optional + Integer + + + Counts the number of times the timer output transitions from 0 to 1. + + + Timer Mode + RW + Single + Optional + Integer + 0..4 + + Type of timer pattern used by the timer. 1: One-shot, 2: On-Time or Interval, 3: Time delay on pick-up, 4: Time Delay on Drop-Out, 0: disables the timer. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3341.xml b/application/src/main/data/lwm2m-registry/3341.xml new file mode 100644 index 0000000000..38e8fb8d9d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3341.xml @@ -0,0 +1,139 @@ + + + + + + + Addressable Text Display + This IPSO object is used to send text to a text-only or text mode graphics display. Writing a string of text to the text resource causes it to be displayed at the selected X and Y locations on the display. If X or Y are set to a value greater than the size of the display, the position "wraps around" to the modulus of the setting and the display size. Likewise, if the text string overflows the display size, the text "wraps around" and displays on the next line down or, if the last line has been written, wraps around to the top of the display. Brightness and Contrast controls are provided to allow control of various display types including STN and DSTN type LCD character displays. Writing an empty payload to the Clear Display resource causes the display to be erased. + + 3341 + urn:oma:lwm2m:ext:3341 + 1.0 + 1.0 + Multiple + Optional + + + Text + RW + Single + Mandatory + String + + + A string of text. + + + X Coordinate + RW + Single + Optional + Integer + + + X Coordinate. + + + Y Coordinate + RW + Single + Optional + Integer + + + Y Coordinate. + + + Max X Coordinate + R + Single + Optional + Integer + + + The highest X coordinate the display supports before wrapping to the next line. + + + Max Y Coordinate + R + Single + Optional + Integer + + + The highest Y coordinate the display supports before wrapping to the next line. + + + Clear Display + E + Single + Optional + + + + Command to clear the display. + + + Level + RW + Single + Optional + Float + 0..100 + /100 + Used to represent a level control such as audio volume. + + + Contrast + RW + Single + Optional + Float + 0..100 + /100 + Proportional control, integer value between 0 and 100 as a percentage. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3342.xml b/application/src/main/data/lwm2m-registry/3342.xml new file mode 100644 index 0000000000..cd27717ed4 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3342.xml @@ -0,0 +1,118 @@ + + + + + + + On/Off switch + This IPSO object should be used with an On/Off switch to report the state of the switch. + 3342 + urn:oma:lwm2m:ext:3342:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Digital Input State + R + Single + Mandatory + Boolean + + + The current state of a digital input. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + On time + RW + Single + Optional + Integer + + s + The time in seconds that the device has been on. Writing a value of 0 resets the counter. + + + Off Time + RW + Single + Optional + Integer + + s + The time in seconds in the off state. Writing a value of 0 resets the counter. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3343.xml b/application/src/main/data/lwm2m-registry/3343.xml new file mode 100644 index 0000000000..3aa109923a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3343.xml @@ -0,0 +1,89 @@ + + + + + + + Dimmer + This IPSO object should be used with a dimmer or level control to report the state of the control. + + 3343 + urn:oma:lwm2m:ext:3343 + 1.0 + 1.0 + Multiple + Optional + + + Level + RW + Single + Mandatory + Float + 0..100 + /100 + Used to represent a level control such as audio volume. + + + On time + RW + Single + Optional + Integer + + s + The time in seconds that the device has been on. Writing a value of 0 resets the counter. + + + Off Time + RW + Single + Optional + Integer + + s + The time in seconds in the off state. Writing a value of 0 resets the counter. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3344.xml b/application/src/main/data/lwm2m-registry/3344.xml new file mode 100644 index 0000000000..2dd8648a19 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3344.xml @@ -0,0 +1,99 @@ + + + + + + + Up/Down Control + This IPSO object is used to report the state of an up/down control element like a pair of push buttons or a rotary encoder. Counters for increase and decrease operations are provided for counting pulses from a quadrature encoder. + + 3344 + urn:oma:lwm2m:ext:3344 + 1.0 + 1.0 + Multiple + Optional + + + Increase Input State + R + Single + Mandatory + Boolean + + + Indicates an increase control action. + + + Decrease Input State + R + Single + Mandatory + Boolean + + + Indicates a decrease control action. + + + Up Counter + RW + Single + Optional + Integer + + + Counts the number of times the increase control has been operated. Writing a 0 resets the counter. + + + Down Counter + RW + Single + Optional + Integer + + + Counts the times the decrease control has been operated. Writing a 0 resets the counter. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3345.xml b/application/src/main/data/lwm2m-registry/3345.xml new file mode 100644 index 0000000000..bb234638a9 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3345.xml @@ -0,0 +1,109 @@ + + + + + + + Multiple Axis Joystick + This IPSO object can be used to report the position of a shuttle or joystick control. A digital input is provided to report the state of an associated push button. + + 3345 + urn:oma:lwm2m:ext:3345 + 1.0 + 1.0 + Multiple + Optional + + + Digital Input State + R + Single + Optional + Boolean + + + The current state of a digital input. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + X Value + R + Single + Optional + Float + + + The measured value along the X axis. + + + Y Value + R + Single + Optional + Float + + + The measured value along the Y axis. + + + Z Value + R + Single + Optional + Float + + + The measured value along the Z axis. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3346.xml b/application/src/main/data/lwm2m-registry/3346.xml new file mode 100644 index 0000000000..d49322f32a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3346.xml @@ -0,0 +1,179 @@ + + + + + + + Rate + This object type should be used to report a rate measurement, for example the speed of a vehicle, or the rotational speed of a drive shaft. It also provides resources for minimum and maximum measured values, as well as the minimum and maximum range that can be measured by the sensor. An example measurement unit is meters per second (m/s). + + 3346 + urn:oma:lwm2m:ext:3346:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor. + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset. + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset. + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor. + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value. + + + Current Calibration + RW + Single + Optional + Float + + + Read or Write the current calibration coefficient. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3347.xml b/application/src/main/data/lwm2m-registry/3347.xml new file mode 100644 index 0000000000..136e3445b9 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3347.xml @@ -0,0 +1,99 @@ + + + + + + + Push button + This IPSO object is used to report the state of a momentary action push button control and to count the number of times the control has been operated since the last observation. + + 3347 + urn:oma:lwm2m:ext:3347:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Digital Input State + R + Single + Mandatory + Boolean + + + The current state of a digital input. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3348.xml b/application/src/main/data/lwm2m-registry/3348.xml new file mode 100644 index 0000000000..9309b4b3dd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3348.xml @@ -0,0 +1,89 @@ + + + + + + + Multi-state Selector + This IPSO object is used to represent the state of a Multi-state selector switch with a number of fixed positions. + + 3348 + urn:oma:lwm2m:ext:3348:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Multi-state Input + R + Single + Mandatory + Integer + + + The current state of a Multi-state input or selector. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3349.xml b/application/src/main/data/lwm2m-registry/3349.xml new file mode 100644 index 0000000000..28029e7a6a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3349.xml @@ -0,0 +1,108 @@ + + + + + + + Bitmap + Summarize several digital inputs to one value by mapping each bit to a digital input. + 3349 + urn:oma:lwm2m:ext:3349:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Bitmap Input + R + Single + Mandatory + Integer + + + Integer in which each of the bits are associated with specific digital input value. Represented as a binary signed integer in network byte order, and in two's complement representation. Using values in range 0-127 is recommended to avoid ambiguities with byte order and negative values. + + + Bitmap Input Reset + E + Single + Optional + + + + Reset the Bitmap Input value. + + + Element Description + RW + Multiple + Optional + String + + + The description of each bit as a string. First instance describes the least significant bit, second instance the second least significant bit. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3350.xml b/application/src/main/data/lwm2m-registry/3350.xml new file mode 100644 index 0000000000..f4f8477fdc --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3350.xml @@ -0,0 +1,128 @@ + + + + + + + Stopwatch + An ascending timer that counts how long time has passed since the timer was started after reset. + 3350 + urn:oma:lwm2m:ext:3350:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Cumulative Time + RW + Single + Mandatory + Float + + s + The total time in seconds that the timer input is true. Writing a 0 resets the time. + + + On/Off + RW + Single + Optional + Boolean + + + On/off control. Boolean value where True is On and False is Off. + + + Digital Input Counter + R + Single + Optional + Integer + + + The cumulative value of active state detected. + + + Application Type + RW + Single + Optional + String + + + The application type of the sensor or actuator as a string depending on the use case. + + + Timestamp + R + Single + Optional + Time + + + The timestamp of when the measurement was performed. + + + Fractional Timestamp + R + Single + Optional + Float + 0..1 + s + Fractional part of the timestamp when sub-second precision is used (e.g., 0.23 for 230 ms). + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. 0: UNCHECKED No quality checks were done because they do not exist or can not be applied. 1: REJECTED WITH CERTAINTY The measured value is invalid. 2: REJECTED WITH PROBABILITY The measured value is likely invalid. 3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. 4: ACCEPTED The measured value is OK. 5-15: Reserved for future extensions. 16-23: Vendor specific measurement quality. + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3351.xml b/application/src/main/data/lwm2m-registry/3351.xml new file mode 100644 index 0000000000..9219a765df --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3351.xml @@ -0,0 +1,131 @@ + + + powerupLog + + 3351 + urn:oma:lwm2m:ext:3351 + 1.0 + 1.0 + Single + Optional + + deviceName + R + Single + Mandatory + String + + + + + toolVersion + R + Single + Mandatory + String + + + + + IMEI + R + Single + Mandatory + String + + + + + IMSI + R + Single + Mandatory + String + + + + + MSISDN + R + Single + Mandatory + String + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3352.xml b/application/src/main/data/lwm2m-registry/3352.xml new file mode 100644 index 0000000000..0e519c4951 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3352.xml @@ -0,0 +1,124 @@ + + + plmnSearchEvent + + 3352 + urn:oma:lwm2m:ext:3352 + 1.0 + 1.0 + Multiple + Optional + + timeScanStart + R + Single + Mandatory + Integer + + + + + plmnID + R + Single + Mandatory + Integer + + + + + BandIndicator + R + Single + Mandatory + Integer + + + + + dlEarfcn + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3353.xml b/application/src/main/data/lwm2m-registry/3353.xml new file mode 100644 index 0000000000..1881a3b3bb --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3353.xml @@ -0,0 +1,123 @@ + + + scellID + + 3353 + urn:oma:lwm2m:ext:3353 + 1.0 + 1.0 + Single + Optional + + plmnID + R + Single + Mandatory + Integer + + + + + BandIndicator + R + Single + Mandatory + Integer + + + + + TrackingAreaCode + R + Single + Mandatory + Integer + + + + + CellID + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3354.xml b/application/src/main/data/lwm2m-registry/3354.xml new file mode 100644 index 0000000000..702be4cabc --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3354.xml @@ -0,0 +1,134 @@ + + + cellReselectionEvent + + 3354 + urn:oma:lwm2m:ext:3354 + 1.0 + 1.0 + Single + Optional + + timeReselectionStart + R + Single + Mandatory + Integer + + + + + dlEarfcn + R + Single + Mandatory + Integer + + + + + CellID + R + Single + Mandatory + Integer + + + + + failureType + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3355.xml b/application/src/main/data/lwm2m-registry/3355.xml new file mode 100644 index 0000000000..fb56230fdf --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3355.xml @@ -0,0 +1,156 @@ + + + handoverEvent + + 3355 + urn:oma:lwm2m:ext:3355 + 1.0 + 1.0 + Single + Optional + + timeHandoverStart + R + Single + Mandatory + Integer + + + + + dlEarfcn + R + Single + Mandatory + Integer + + + + + CellID + R + Single + Mandatory + Integer + + + + + handoverResult + R + Single + Mandatory + Integer + + + + + + TargetEarfcn + R + Single + Mandatory + Integer + + + + + TargetPhysicalCellID + R + Single + Mandatory + Integer + + + + + targetCellRsrp + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3356.xml b/application/src/main/data/lwm2m-registry/3356.xml new file mode 100644 index 0000000000..95c7fc7835 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3356.xml @@ -0,0 +1,120 @@ + + + radioLinkFailureEvent + + 3356 + urn:oma:lwm2m:ext:3356 + 1.0 + 1.0 + Single + Optional + + timeRLF + R + Single + Mandatory + Integer + + + + + rlfCause + R + Single + Mandatory + Integer + + + + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3357.xml b/application/src/main/data/lwm2m-registry/3357.xml new file mode 100644 index 0000000000..935b7c4ba7 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3357.xml @@ -0,0 +1,131 @@ + + + rrcStateChangeEvent + + 3357 + urn:oma:lwm2m:ext:3357 + 1.0 + 1.0 + Single + Optional + + rrcState + R + Single + Mandatory + Integer + + + + + rrcStateChangeCause + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3358.xml b/application/src/main/data/lwm2m-registry/3358.xml new file mode 100644 index 0000000000..2784d6a990 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3358.xml @@ -0,0 +1,106 @@ + + + rrcTimerExpiryEvent + + 3358 + urn:oma:lwm2m:ext:3358 + 1.0 + 1.0 + Single + Optional + + RrcTimerExpiryEvent + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3359.xml b/application/src/main/data/lwm2m-registry/3359.xml new file mode 100644 index 0000000000..91cb845e7a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3359.xml @@ -0,0 +1,105 @@ + + + cellBlacklistEvent + + 3359 + urn:oma:lwm2m:ext:3359 + 1.0 + 1.0 + Single + Optional + + dlEarfcn + R + Single + Mandatory + Integer + + + + + CellID + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3360.xml b/application/src/main/data/lwm2m-registry/3360.xml new file mode 100644 index 0000000000..12b2302854 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3360.xml @@ -0,0 +1,128 @@ + + + esmContextInfo + + 3360 + urn:oma:lwm2m:ext:3360 + 1.0 + 1.0 + Single + Optional + + contextType + R + Single + Mandatory + Integer + + + + + bearerState + R + Single + Mandatory + Integer + + + + + radioBearerId + R + Single + Mandatory + Integer + + + + + qci + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3361.xml b/application/src/main/data/lwm2m-registry/3361.xml new file mode 100644 index 0000000000..3d354bffac --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3361.xml @@ -0,0 +1,130 @@ + + + emmStateValue + + 3361 + urn:oma:lwm2m:ext:3361 + 1.0 + 1.0 + Single + Optional + + EmmState + R + Single + Mandatory + Integer + + + + + emmSubstate + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3362.xml b/application/src/main/data/lwm2m-registry/3362.xml new file mode 100644 index 0000000000..3cb73eb79a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3362.xml @@ -0,0 +1,97 @@ + + + nasEmmTimerExpiryEvent + + 3362 + urn:oma:lwm2m:ext:3362 + 1.0 + 1.0 + Single + Optional + + NasEmmTimerExpiryEvent + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3363.xml b/application/src/main/data/lwm2m-registry/3363.xml new file mode 100644 index 0000000000..3b44b91f26 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3363.xml @@ -0,0 +1,100 @@ + + + nasEsmExpiryEvent + + 3363 + urn:oma:lwm2m:ext:3363 + 1.0 + 1.0 + Single + Optional + + NasEsmExpiryEvent + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3364.xml b/application/src/main/data/lwm2m-registry/3364.xml new file mode 100644 index 0000000000..0a042d12d2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3364.xml @@ -0,0 +1,97 @@ + + + emmFailureCauseEvent + + 3364 + urn:oma:lwm2m:ext:3364 + 1.0 + 1.0 + Single + Optional + + EMMCause + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3365.xml b/application/src/main/data/lwm2m-registry/3365.xml new file mode 100644 index 0000000000..83dbf0c807 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3365.xml @@ -0,0 +1,123 @@ + + + rachLatency_delay + + 3365 + urn:oma:lwm2m:ext:3365 + 1.0 + 1.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + rachLatencyVal + R + Single + Mandatory + Integer + + + + + delay + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3366.xml b/application/src/main/data/lwm2m-registry/3366.xml new file mode 100644 index 0000000000..5498af2a3d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3366.xml @@ -0,0 +1,176 @@ + + + macRachAttemptEvent + + 3366 + urn:oma:lwm2m:ext:3366 + 1.0 + 1.0 + Single + Optional + + rachAttemptCounter + R + Single + Mandatory + Integer + + + + + MacRachAttemptEventType + R + Single + Mandatory + Integer + + + + + contentionBased + R + Single + Mandatory + Boolean + + + + + rachMessage + R + Single + Mandatory + Integer + + + + + preambleIndex + R + Single + Mandatory + Integer + + + + + preamblePowerOffset + R + Single + Mandatory + Integer + + + + + backoffTime + R + Single + Mandatory + Integer + + + + + msg2Result + R + Single + Mandatory + Boolean + + + + + timingAdjustmentValue + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3367.xml b/application/src/main/data/lwm2m-registry/3367.xml new file mode 100644 index 0000000000..1f35cf4cc5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3367.xml @@ -0,0 +1,138 @@ + + + macRachAttemptReasonEvent + + 3367 + urn:oma:lwm2m:ext:3367 + 1.0 + 1.0 + Single + Optional + + MacRachAttemptReasonType + R + Single + Mandatory + Integer + + + + + ueID + R + Single + Mandatory + Integer + + + + + contentionBased + R + Single + Mandatory + Boolean + + + + + preamble + R + Single + Mandatory + Integer + + + + + preambleGroupChosen + R + Single + Mandatory + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3368.xml b/application/src/main/data/lwm2m-registry/3368.xml new file mode 100644 index 0000000000..40c17bfd37 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3368.xml @@ -0,0 +1,100 @@ + + + macTimerStatusEvent + + 3368 + urn:oma:lwm2m:ext:3368 + 1.0 + 1.0 + Single + Optional + + macTimerName + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3369.xml b/application/src/main/data/lwm2m-registry/3369.xml new file mode 100644 index 0000000000..b93bb86644 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3369.xml @@ -0,0 +1,105 @@ + + + macTimingAdvanceEvent + + 3369 + urn:oma:lwm2m:ext:3369 + 1.0 + 1.0 + Single + Optional + + timerValue + R + Single + Mandatory + Integer + + + + + timingAdvance + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3370.xml b/application/src/main/data/lwm2m-registry/3370.xml new file mode 100644 index 0000000000..667ab0a281 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3370.xml @@ -0,0 +1,141 @@ + + + ServingCellMeasurement + + 3370 + urn:oma:lwm2m:ext:3370 + 1.0 + 1.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + pci + R + Single + Mandatory + Integer + + + + + rsrp + R + Single + Mandatory + Integer + + + + + rsrq + R + Single + Mandatory + Integer + + + + + dlEarfcn + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3371.xml b/application/src/main/data/lwm2m-registry/3371.xml new file mode 100644 index 0000000000..fbc0860870 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3371.xml @@ -0,0 +1,141 @@ + + + NeighborCellMeasurements + + 3371 + urn:oma:lwm2m:ext:3371 + 1.0 + 1.0 + Multiple + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + pci + R + Single + Mandatory + Integer + + + + + rsrp + R + Single + Mandatory + Integer + + + + + rsrq + R + Single + Mandatory + Integer + + + + + dlEarfcn + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3372.xml b/application/src/main/data/lwm2m-registry/3372.xml new file mode 100644 index 0000000000..49d585a973 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3372.xml @@ -0,0 +1,115 @@ + + + TimingAdvance + + 3372 + urn:oma:lwm2m:ext:3372 + 1.0 + 1.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + timingAdvance + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3373.xml b/application/src/main/data/lwm2m-registry/3373.xml new file mode 100644 index 0000000000..fb2af328b3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3373.xml @@ -0,0 +1,115 @@ + + + txPowerHeadroomEvent + + 3373 + urn:oma:lwm2m:ext:3373 + 1.0 + 1.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + headroom-value + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3374.xml b/application/src/main/data/lwm2m-registry/3374.xml new file mode 100644 index 0000000000..be7ecdc34e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3374.xml @@ -0,0 +1,132 @@ + + + radioLinkMonitoring + + 3374 + urn:oma:lwm2m:ext:3374 + 1.0 + 1.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + outOfSyncCount + R + Single + Mandatory + Integer + + + + + inSyncCount + R + Single + Mandatory + Integer + + + + + t310Timer + R + Single + Mandatory + Boolean + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3375.xml b/application/src/main/data/lwm2m-registry/3375.xml new file mode 100644 index 0000000000..75fb2a5a42 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3375.xml @@ -0,0 +1,150 @@ + + + PagingDRX + + 3375 + urn:oma:lwm2m:ext:3375 + 1.0 + 1.0 + Single + Optional + + dlEarfcn + R + Single + Mandatory + Integer + + + + + pci + R + Single + Mandatory + Integer + + + + + pagingCycle + R + Single + Mandatory + Integer + + + + + DrxNb + R + Single + Mandatory + Integer + + + + + ueID + R + Single + Mandatory + Integer + + + + + drxSysFrameNumOffset + R + Single + Mandatory + Integer + + + + + drxSubFrameNumOffset + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3376.xml b/application/src/main/data/lwm2m-registry/3376.xml new file mode 100644 index 0000000000..542f816da0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3376.xml @@ -0,0 +1,97 @@ + + + txPowerBackOffEvent + + 3376 + urn:oma:lwm2m:ext:3376 + 1.0 + 1.0 + Single + Optional + + TxPowerBackoff + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3377.xml b/application/src/main/data/lwm2m-registry/3377.xml new file mode 100644 index 0000000000..515eb36439 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3377.xml @@ -0,0 +1,168 @@ + + + Message3Report + + 3377 + urn:oma:lwm2m:ext:3377 + 1.0 + 1.0 + Single + Optional + + tpc + R + Single + Mandatory + Integer + + + + + resourceIndicatorValue + R + Single + Mandatory + Integer + + + + + cqi + R + Single + Mandatory + Integer + + + + + uplinkDelay + R + Single + Mandatory + Boolean + + + + + hoppingEnabled + R + Single + Mandatory + Boolean + + + + + numRb + R + Single + Mandatory + Integer + + + + + transportBlockSizeIndex + R + Single + Mandatory + Integer + + + + + ModulationType + R + Single + Mandatory + Integer + + + + + redundancyVersionIndex + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3378.xml b/application/src/main/data/lwm2m-registry/3378.xml new file mode 100644 index 0000000000..970d245a90 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3378.xml @@ -0,0 +1,115 @@ + + + PbchDecodingResults + + 3378 + urn:oma:lwm2m:ext:3378 + 1.0 + 1.0 + Single + Optional + + servingCellID + R + Single + Mandatory + Integer + + + + + crcResult + R + Single + Mandatory + Boolean + + + + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3379.xml b/application/src/main/data/lwm2m-registry/3379.xml new file mode 100644 index 0000000000..87137da530 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3379.xml @@ -0,0 +1,123 @@ + + + pucchPowerControl + + 3379 + urn:oma:lwm2m:ext:3379 + 1.0 + 1.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + pucchTxPowerValue + R + Single + Mandatory + Integer + + + + + dlPathLoss + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3380.xml b/application/src/main/data/lwm2m-registry/3380.xml new file mode 100644 index 0000000000..8290651569 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3380.xml @@ -0,0 +1,204 @@ + + + PrachReport + + 3380 + urn:oma:lwm2m:ext:3380:2.0 + 1.0 + 2.0 + Single + Optional + + sysFrameNumber + R + Single + Mandatory + Integer + + + + + subFrameNumber + R + Single + Mandatory + Integer + + + + + rachTxPower + R + Single + Mandatory + Integer + + + + + zadOffSeqNum + R + Single + Mandatory + Integer + + + + + prachConfig + R + Single + Mandatory + Integer + + + + + preambleFormat + R + Single + Mandatory + Integer + + + + + maxTransmissionMsg3 + R + Single + Mandatory + Integer + + + + + raResponseWindowSize + R + Single + Mandatory + Integer + + + + + RachRequestResult + R + Single + Mandatory + Boolean + + + + + ce_mode + R + Single + Mandatory + Integer + + + + + ce_level + R + Single + Mandatory + Integer + + + + + num_prach_repetition + R + Single + Mandatory + Integer + + + + + prach_repetition_seq + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3381.xml b/application/src/main/data/lwm2m-registry/3381.xml new file mode 100644 index 0000000000..db2e4d52e8 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3381.xml @@ -0,0 +1,110 @@ + + + VolteCallEvent + + 3381 + urn:oma:lwm2m:ext:3381 + 1.0 + 1.0 + Single + Optional + + callStatus + R + Single + Mandatory + Integer + + + + + callType + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3382.xml b/application/src/main/data/lwm2m-registry/3382.xml new file mode 100644 index 0000000000..8f066837c8 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3382.xml @@ -0,0 +1,108 @@ + + + SipRegistrationEvent + + 3382 + urn:oma:lwm2m:ext:3382 + 1.0 + 1.0 + Single + Optional + + registrationType + R + Single + Mandatory + Integer + + + + + registrationResult + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3383.xml b/application/src/main/data/lwm2m-registry/3383.xml new file mode 100644 index 0000000000..663ac45faa --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3383.xml @@ -0,0 +1,99 @@ + + + sipPublishEvent + + 3383 + urn:oma:lwm2m:ext:3383 + 1.0 + 1.0 + Single + Optional + + publishResult + R + Single + Mandatory + Integer + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3384.xml b/application/src/main/data/lwm2m-registry/3384.xml new file mode 100644 index 0000000000..a18c6b910b --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3384.xml @@ -0,0 +1,112 @@ + + + sipSubscriptionEvent + + 3384 + urn:oma:lwm2m:ext:3384 + 1.0 + 1.0 + Single + Optional + + eventType + R + Single + Mandatory + Integer + + + + + subscriptionResult + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3385.xml b/application/src/main/data/lwm2m-registry/3385.xml new file mode 100644 index 0000000000..cc0a99182a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3385.xml @@ -0,0 +1,110 @@ + + + volteCallStateChangeEvent + + 3385 + urn:oma:lwm2m:ext:3385 + 1.0 + 1.0 + Single + Optional + + callStatus + R + Single + Mandatory + Integer + + + + + VolteCallStateChangeCause + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3386.xml b/application/src/main/data/lwm2m-registry/3386.xml new file mode 100644 index 0000000000..43937efadb --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3386.xml @@ -0,0 +1,105 @@ + + + VoLTErtpPacketLoss + 1]]> + 3386 + urn:oma:lwm2m:ext:3386 + 1.0 + 1.0 + Single + Optional + + ssrc + R + Single + Mandatory + Integer + + + + + packetsLost + R + Single + Mandatory + Integer + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3387.xml b/application/src/main/data/lwm2m-registry/3387.xml new file mode 100644 index 0000000000..c50ecb47bc --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3387.xml @@ -0,0 +1,224 @@ + + + + + + + oA Basic Control + + 3387 + urn:oma:lwm2m:ext:3387 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Control Function Status + R + Single + Mandatory + Integer + 0: disabled + 1: normal + 2: remote + 3: local + 255: non_responsive + + + + + Ghost Status + R + Single + Mandatory + Boolean + + + + + + Control Object Mode + RW + Single + Mandatory + Integer + 1: normal + 2: remote + 3: local + + + + + Ghost Configuration + RW + Single + Optional + Boolean + 0..65535 + + + + + Supersede Heartbeat Time + RW + Single + Mandatory + Integer + + s + + + + Controlled Application Group ID + RW + Single + Mandatory + Integer + 0..65535 + + + + + Push-Button Event Configuration + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Presence Sensor Event Configuration + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Supersede Heartbeat + W + Single + Optional + Unsigned Integer + 0..65535 + s + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + RW + Single + Optional + Unsigned Integer + 0..65535 + + + + + Execute Inject + E + Single + Optional + + + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + Active-Controller-List + R + Multiple + Optional + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3388.xml b/application/src/main/data/lwm2m-registry/3388.xml new file mode 100644 index 0000000000..c1b5e54206 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3388.xml @@ -0,0 +1,178 @@ + + + + + + + oA Device + + 3388 + urn:oma:lwm2m:ext:3388 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Error Status + R + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + OEM ID + R + Single + Optional + String + 0..64 bytes + + + + + OEM String + R + Single + Optional + String + 0..64 bytes + + + + + BMS ID + RW + Single + Optional + Unsigned Integer + 0..65535 + + + + + CPU Temperature + R + Single + Mandatory + Integer + -128..127 + C + + + + Executing Objects + R + Multiple + Mandatory + Corelnk + + + + + + Total Energy Usage + R + Single + Mandatory + Unsigned Integer + 0..18446744073709551615 + Ws + + + + Actual Power Usage + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + W + + + + Accuracy Class + R + Single + Optional + String + 0..64 bytes + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 bytes + + + + + System Failure Time + RW + Single + Optional + Unsigned Integer + 0..65535 + ms + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3389.xml b/application/src/main/data/lwm2m-registry/3389.xml new file mode 100644 index 0000000000..7e3d0c7cdb --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3389.xml @@ -0,0 +1,108 @@ + + + + + + + oA Group + + 3389 + urn:oma:lwm2m:ext:3389 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Security Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + IP Addresses + RW + Multiple + Mandatory + Opaque + + + + + + Members + RW + Multiple + Mandatory + Corelnk + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3390.xml b/application/src/main/data/lwm2m-registry/3390.xml new file mode 100644 index 0000000000..0e56d0a19a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3390.xml @@ -0,0 +1,298 @@ + + + + + + + oA Logical Colour Light-Point Actuator + + 3390 + urn:oma:lwm2m:ext:3390 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Target Colour X + R + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Target Colour Y + R + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Priority + RW + Single + Mandatory + Unsigned Integer + 0..5 + + + + + Colour Changing Time + RW + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Colour Transition Time + RW + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Hue Step Size + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Saturation Step Size + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Scene Cache + RW + Multiple + Optional + Opaque + + + + + + Set Colour + W + Single + Mandatory + Opaque + + + + + + Relative Change Saturation + W + Single + Mandatory + Boolean + + + ////118 0` \ndecreases saturation.\n- `POST coap://////118 1` \nincreases saturation.]]> + + + Relative Change Hue + W + Single + Mandatory + Boolean + + + ////118 0` \nincreases hue.\n- `POST coap://////118 1` \ndecreases hue.]]> + + + Stop Transition + E + Single + Mandatory + + + + + + + Step Saturation + W + Single + Mandatory + Opaque + + + + + + Step Hue + W + Single + Mandatory + Opaque + + + + + + Recall Scene + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + RW + Single + Optional + Unsigned Integer + 0..65535 + + + + + Execute Inject + E + Single + Optional + + + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3391.xml b/application/src/main/data/lwm2m-registry/3391.xml new file mode 100644 index 0000000000..0b4ce12cd0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3391.xml @@ -0,0 +1,258 @@ + + + + + + + oA Logical Colour Temperature Light-Point Actuator + + 3391 + urn:oma:lwm2m:ext:3391 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Target Colour Temperature + R + Single + Mandatory + Unsigned Integer + 0..65535 + K + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Priority + RW + Single + Mandatory + Unsigned Integer + 0..5 + + + + + Colour Temperature Changing Time + RW + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Colour Temperature Transition Time + RW + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Colour Temperature Step Size + RW + Single + Mandatory + Unsigned Integer + 0..65535 + K + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Scene Cache + RW + Multiple + Optional + Opaque + + + + + + Set Colour Temperature + W + Single + Mandatory + Opaque + + + + + + Relative Change Colour Temperature + W + Single + Mandatory + Boolean + + + ////118 0` \nreduces the colour temperature.\n- `POST coap://////118 1` \nincreases the colour temperature.]]> + + + Stop Transition + E + Single + Mandatory + + + + + + + Step Colour Temperature + W + Single + Mandatory + Opaque + + + + + + Recall Scene + W + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + W + Single + Optional + Unsigned Integer + 0..65535 + + + + + Execute Test Injection + E + Single + Optional + + + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3392.xml b/application/src/main/data/lwm2m-registry/3392.xml new file mode 100644 index 0000000000..07b1b154e4 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3392.xml @@ -0,0 +1,198 @@ + + + + + + + oA Logical Illuminance Sensor + + 3392 + urn:oma:lwm2m:ext:3392 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Sensor Value + R + Single + Mandatory + Unsigned Integer + 0..65535 + lx + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Less Than + RW + Single + Optional + Unsigned Integer + 0..65535 + lx + + + + Greater Than + RW + Single + Optional + Unsigned Integer + 0..65535 + lx + + + + Step + RW + Single + Optional + Unsigned Integer + 0..65535 + lx + + + + Minimum Update Interval + RW + Single + Mandatory + Unsigned Integer + 0..65535 + s + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + W + Single + Optional + Unsigned Integer + 0..65535 + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + Object Enabled + E + Single + Optional + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3393.xml b/application/src/main/data/lwm2m-registry/3393.xml new file mode 100644 index 0000000000..e3c883ac70 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3393.xml @@ -0,0 +1,268 @@ + + + + + + + oA Logical Light-Point Actuator + + 3393 + urn:oma:lwm2m:ext:3393 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Target OnOff + R + Single + Mandatory + Boolean + + + + + + Target Intensity + R + Single + Mandatory + Float + 0.0..1.0 + + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Priority + RW + Single + Mandatory + Unsigned Integer + 0..5 + + + + + Dimming Time + RW + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Transition Time + RW + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Step Size + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Scene Cache + RW + Multiple + Optional + Opaque + + + + + + Switch + W + Single + Mandatory + Opaque + + + ////117 {0:false,2:2}` \nor\n`POST coap://////117 {1:0,2:2}` \nsets the light point to Off and takes 200ms to set the intensity to '0'\n- `POST coap://////117 {0:true,2:2}` \nsets the light point to On and takes 200ms to set the intensity to the intensity that was active before the lightpoint was switched off.\n- `POST coap://////117 {1:1,2:2}` \nsets the light point to On and takes 200ms to set the intensity to '1'\n\t\t\t\n**Note:** When the optional argument time is not given, or if its value is empty the default configuration value is used.\nA request on this Resource will immediately impact the value of 'On/Off Status' and a continuous update of 'Remaining Transition Time' and 'Current Intensity' until the Switch operation is cloncluded.]]> + + + Dim + W + Single + Mandatory + Boolean + + + ////118 0` \ndims the light points down.\n- `POST coap://////118 1` \ndims the light point up.\n\t\t\t\t\nDimming is also possible during off. This effects the intensity that 'On' requests without requested intensity will execute.]]> + + + Stop Transition + E + Single + Mandatory + + + + + + + Step + W + Single + Mandatory + Opaque + + + + + + Recall Scene + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + W + Single + Optional + Unsigned Integer + 0..65535 + + + + + Object Enabled + E + Single + Optional + + + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3394.xml b/application/src/main/data/lwm2m-registry/3394.xml new file mode 100644 index 0000000000..767100e145 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3394.xml @@ -0,0 +1,177 @@ + + + + + + oA Logical Presence Sensor + + 3394 + urn:oma:lwm2m:ext:3394 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Digital Input State + R + Single + Mandatory + Boolean + + + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Busy to Clear delay + RW + Single + Optional + Integer + + ms + + + + Clear to Busy delay + RW + Single + Optional + Integer + + ms + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + W + Single + Optional + Boolean + + + + + + Object Enabled + E + Single + Optional + + + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3395.xml b/application/src/main/data/lwm2m-registry/3395.xml new file mode 100644 index 0000000000..29b4ce6b67 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3395.xml @@ -0,0 +1,182 @@ + + + + + + + oA Logical Push-Button Sensor + + 3395 + urn:oma:lwm2m:ext:3395 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Push-Button Event Value + R + Single + Mandatory + Unsigned Integer + 0: RELEASE + 1: CLICK + 2: HOLD + 3: DOUBLE-CLICK + 255: STUCK + + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Single Click Time + RW + Single + Mandatory + Unsigned Integer + 100..60000 + ms + + + + Hold Repeat Time + RW + Single + Optional + Unsigned Integer + 300..60000 + ms + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Debug Mode Enabled + RW + Single + Optional + Boolean + + + + + + Inject Test Event + W + Single + Optional + Integer + 0..65535 + + + + + Object Enabled + E + Single + Optional + + + + + + + Object Enabled + RW + Single + Optional + Boolean + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3396.xml b/application/src/main/data/lwm2m-registry/3396.xml new file mode 100644 index 0000000000..c3183f8e29 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3396.xml @@ -0,0 +1,270 @@ + + + + + + + oA Physical Colour Light-Point Actuator + + 3396 + urn:oma:lwm2m:ext:3396 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Error Status + R + Single + Mandatory + Opaque + + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 Bytes + B + + + + On/Off + RW + Single + Mandatory + Boolean + + + + + + Current Intensity + R + Single + Mandatory + Float + 0.0..1.0 + + + + + Remaining Transition Time + R + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Current Colour Y + R + Single + Mandatory + Unsigned Integer + 0..10000 + K + + + + Current Colour X + R + Single + Mandatory + Unsigned Integer + 0..10000 + K + + + + Remaining Colour Transition Time + R + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Total Energy Usage + R + Single + Mandatory + Unsigned Integer + 0..18446744073709551615 + Ws + + + + Actual Power Usage + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + W + + + + Accuracy Class + R + Single + Optional + String + 0..64 bytes + + + + + Operating Hours + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + h + + + + Adjusted Operating Hours + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + + + + + Physical Minimum Intensity + R + Single + Optional + Float + 0.000001..1.0 + + + + + Minimum Intensity + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Maximum Intensity + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Power On Behaviour + RW + Single + Optional + Unsigned Integer + 0: CONFIGURED_ON + 1: FULL_ON + 2: RECALL + + + + + Power On Intensity + RW + Single + Optional + Float + 0.000001..1.0 + + + + + Stored Intensity + R + Single + Optional + Float + 0.000001..1.0 + + + + + System Failure Intensity + RW + Single + Optional + Float + 0.000001..1.0 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3397.xml b/application/src/main/data/lwm2m-registry/3397.xml new file mode 100644 index 0000000000..b8a3bed8d0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3397.xml @@ -0,0 +1,302 @@ + + + + + + + oA Physical Colour Temperature Light-Point Actuator + + 3397 + urn:oma:lwm2m:ext:3397 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Error Status + R + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 bytes + + + + + On/Off + RW + Single + Mandatory + Boolean + + + + + + Current Intensity + R + Single + Mandatory + Float + 0.0..1.0 + + + + + Remaining Transition Time + R + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + + + Current Colour Temperature + R + Single + Mandatory + Unsigned Integer + 0..65535 + K + + + + Remaining Colour Temperature Transition Time + R + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Total Energy Usage + R + Single + Mandatory + Unsigned Integer + 0..18446744073709551615 + Ws + + + + Actual Power Usage + R + Single + Mandatory + Integer + 0..4294967295 + W + + + + Accuracy Class + R + Single + Optional + String + 0..64 bytes + + + + + Operating Hours + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + h + + + + Adjusted Operating Hours + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + + + + + Physical Minimum Intensity + R + Single + Optional + Float + 0.000001..1.0 + + + + + Minimum Intensity + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Maximum Intensity + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Physical Minimum Colour Temperature + R + Single + Optional + Unsigned Integer + 0..65535 + K + + + + Physical Maximum Colour Temperature + R + Single + Optional + Unsigned Integer + 0..65535 + K + + + + Minimum Colour Temperature + R + Single + Optional + Unsigned Integer + 0..65535 + K + + + + Maximum Colour Temperature + R + Single + Optional + Unsigned Integer + 0..65535 + K + + + + Power On Behaviour + RW + Single + Optional + Unsigned Integer + 0: CONFIGURED_ON + 1: FULL_ON + 2: RECALL + + + + + Power On Intensity + RW + Single + Optional + Float + 0.000001..1.0 + + + + + Stored Intensity + R + Single + Optional + Float + 0.000001..1.0 + + + + + System Failure Intensity + RW + Single + Optional + Float + 0.000001..1.0 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3398.xml b/application/src/main/data/lwm2m-registry/3398.xml new file mode 100644 index 0000000000..de3e24113d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3398.xml @@ -0,0 +1,107 @@ + + + + + + oA Physical Illuminance Sensor + + 3398 + urn:oma:lwm2m:ext:3398 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Min Range Value + R + Single + Optional + Unsigned Integer + 0..65535 + lx + + + + Max Range Value + R + Single + Optional + Unsigned Integer + 0..65535 + lx + + + + Error Status + R + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 bytes + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3399.xml b/application/src/main/data/lwm2m-registry/3399.xml new file mode 100644 index 0000000000..8c29c4af82 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3399.xml @@ -0,0 +1,238 @@ + + + + + oA Physical Light-Point Actuator + + 3399 + urn:oma:lwm2m:ext:3399 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Error Status + R + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 bytes + + + + + On/Off + RW + Single + Mandatory + Boolean + + + + + + Current Intensity + R + Single + Mandatory + Float + 0.0..1.0 + + + + + Remaining Transition Time + R + Single + Mandatory + Unsigned Integer + 0..65535 + ms + + + + Total Energy Usage + R + Single + Mandatory + Unsigned Integer + 0..18446744073709551615 + Ws + + + + Actual Power Usage + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + W + + + + Accuracy Class + R + Single + Optional + String + 0..64 bytes + + + + + Operating Hours + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + h + + + + Adjusted Operating Hours + R + Single + Mandatory + Unsigned Integer + 0..4294967295 + + + + + Physical Minimum Intensity + R + Single + Optional + Float + 0.000001..1.0 + + + + + Minimum Intensity + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Maximum Intensity + RW + Single + Mandatory + Float + 0.000001..1.0 + + + + + Power On Behaviour + RW + Single + Optional + Unsigned Integer + 0: CONFIGURED_ON + 1: FULL_ON + 2: RECALL + + + + + Power On Intensity + RW + Single + Optional + Float + 0.000001..1.0 + + + + + Stored Intensity + R + Single + Optional + Float + 0.000001..1.0 + + + + + System Failure Intensity + RW + Single + Optional + Float + 0.000001..1.0 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3400.xml b/application/src/main/data/lwm2m-registry/3400.xml new file mode 100644 index 0000000000..aea142daba --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3400.xml @@ -0,0 +1,88 @@ + + + + + + + oA Physical Presence Sensor + + 3400 + urn:oma:lwm2m:ext:3400 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Error Status + R + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 bytes + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3401.xml b/application/src/main/data/lwm2m-registry/3401.xml new file mode 100644 index 0000000000..1b088f1737 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3401.xml @@ -0,0 +1,120 @@ + + + + + + + oA Physical Push-Button Sensor + + 3401 + urn:oma:lwm2m:ext:3401 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Push-Button Status Value + R + Single + Mandatory + Unsigned Integer + 0: RELEASED + 1: PRESSED + 2: FAULT + + + + + Error Status + R + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Mounting Location + RW + Single + Mandatory + String + 1..64 bytes + + + + + Digital Input Polarity + RW + Single + Mandatory + Boolean + + + + + + Digital Input Debounce + RW + Single + Mandatory + Integer + + ms + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3402.xml b/application/src/main/data/lwm2m-registry/3402.xml new file mode 100644 index 0000000000..5cf405a66a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3402.xml @@ -0,0 +1,88 @@ + + + + + + + oA Receiving Object + + 3402 + urn:oma:lwm2m:ext:3402 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Executing Object + RW + Single + Mandatory + Corelnk + + + + + + Status Report + W + Single + Mandatory + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3403.xml b/application/src/main/data/lwm2m-registry/3403.xml new file mode 100644 index 0000000000..f3690e5df5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3403.xml @@ -0,0 +1,118 @@ + + + + + + + oA Reporting Object + + 3403 + urn:oma:lwm2m:ext:3403 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Reporting Object Instances + RW + Multiple + Mandatory + Corelnk + + + + + + Target Resource + RW + Single + Mandatory + Corelnk + + + + + + Status Resend Time + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 1..600 + s + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + s + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3404.xml b/application/src/main/data/lwm2m-registry/3404.xml new file mode 100644 index 0000000000..24250ab608 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3404.xml @@ -0,0 +1,118 @@ + + + + + + + oA Scene + + 3404 + urn:oma:lwm2m:ext:3404 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Scene ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Application Group ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Changeable by User API + RW + Single + Mandatory + Boolean + + + + + + Transition Time + RW + Single + Mandatory + Unsigned Integer + 0..60000 + ms + + + + Scene Configuration + RW + Multiple + Mandatory + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3405.xml b/application/src/main/data/lwm2m-registry/3405.xml new file mode 100644 index 0000000000..f6939917ae --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3405.xml @@ -0,0 +1,98 @@ + + + + + + + oA OGC-Security + + 3405 + urn:oma:lwm2m:ext:3405 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + OGC Security ID + RW + Single + Mandatory + Unsigned Integer + 0..65535 + + + + + Accepted Senders + RW + Multiple + Mandatory + Opaque + + + + + + Secret Group Key + W + Single + Mandatory + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3406.xml b/application/src/main/data/lwm2m-registry/3406.xml new file mode 100644 index 0000000000..ccc7601eeb --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3406.xml @@ -0,0 +1,88 @@ + + + + + + + oA Status Report Structure + + 3406 + urn:oma:lwm2m:ext:3406 + 1.1 + 1.0 + Multiple + Optional + + + ObjectVersion + R + Single + Optional + String + + + + + + Documentary Description + RW + Single + Optional + String + 0..256 bytes + + + + + Status Report Structure ID + RW + Single + Mandatory + Unsigned Integer + 0..255 + + + + + Keys + RW + Multiple + Mandatory + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3407.xml b/application/src/main/data/lwm2m-registry/3407.xml new file mode 100644 index 0000000000..7f29324566 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3407.xml @@ -0,0 +1,98 @@ + + + + + + + Smoke Alarm + + 3407 + urn:oma:lwm2m:ext:3407 + 1.1 + 1.0 + Multiple + Optional + + + Battery Percentage + R + Single + Optional + Float + 0.00..100.00 + + + + + RSSI + R + Single + Optional + Float + + dBW + + + + Smoke Sensor State + R + Single + Mandatory + Boolean + + + + + + Detected CO2 percentage + R + Single + Optional + Float + 0.00..100.00 + /100 + + + + Alarm loudness + R + Single + Optional + Float + + dB + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3408.xml b/application/src/main/data/lwm2m-registry/3408.xml new file mode 100644 index 0000000000..4d5ff67eef --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3408.xml @@ -0,0 +1,79 @@ + + + + Manhole Cover + + 3408 + urn:oma:lwm2m:ext:3408 + 1.1 + 1.0 + Multiple + Optional + + + Safety Angle Threshold + R + Single + Mandatory + Integer + 0..180 + rad + + + + Manhole Cover Tilt + R + Single + Mandatory + Integer + 0..180 + rad + + + + Cover Open Status + R + Single + Mandatory + Boolean + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/3410.xml b/application/src/main/data/lwm2m-registry/3410.xml new file mode 100644 index 0000000000..63449e590b --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3410.xml @@ -0,0 +1,148 @@ + + + + + + + Device Extension + The uCIFI device extension is an extension to the standard Lightweight M2M device (object ID 3) using the resource called "ExtDevInfo" (resource ID 22). + 3410 + urn:oma:lwm2m:ext:3410 + 1.0 + 1.0 + Single + Optional + + + GTIN model number + R + Single + Optional + String + + + Standard global trade-in international number for the control device. + + + Manufacturer identifier + R + Single + Mandatory + String + + + Unique identifier of the manufacturer of the control device, equivalent to IEEE's OUI (Organizational Unique Identifier). This value should be a Hexadecimal value. This value may be used by an online uCIFI product catalog server, for software modules to retrieve the data model of a device using this attribute associated with the model number on the "Device" (Id=3) object. + + + User-given name + RW + Single + Optional + String + + + User-readable name of the device set by the user. + + + Asset identifier + RW + Single + Optional + String + + + Identifier of the asset (e.g. meter, luminaire, container) that is controlled by the device. + + + Installation date + RW + Single + Optional + Time + + + Installation date of the device. + + + Software update + R + Single + Optional + Boolean + + + Set to True while software within the device is being updated. + + + Maintenance + RW + Single + Optional + Boolean + + + Set to True when the device is in maintenance mode. + + + Configuration reset + E + Single + Optional + + + + Reset the configuration parameters (and only the configuration parameters) of the device. It does not clear any counter (e.g. kWh counter or operating hour counter). + + + Device operating hours + R + Single + Optional + Integer + + h + Cumulated number of hours during which the device has been powered on. + + + Additional firmware information + R + Single + Optional + String + + + Additional information about peripheral firmware versions. The format is left to the vendor. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3411.xml b/application/src/main/data/lwm2m-registry/3411.xml new file mode 100644 index 0000000000..6339b847bd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3411.xml @@ -0,0 +1,168 @@ + + + + + + + Battery + The uCIFI battery object provides attributes to monitor battery level and activity. + 3411 + urn:oma:lwm2m:ext:3411 + 1.0 + 1.0 + Multiple + Optional + + + Battery level + R + Single + Mandatory + Integer + + /100 + Level of charge of the battery in % of total possible charge. + + + Battery capacity + R + Single + Optional + Float + + Ah + Nominal capacity of the battery in Ampere hour. + + + Battery voltage + R + Single + Optional + Float + + V + Level of charge of the battery in Volts. + + + Type of battery + RW + Single + Optional + String + + + Describes the type of battery (e.g. Li-Ion rechargeable). + + + Low battery threshold + RW + Single + Optional + Integer + + /100 + Threshold below which an event is generated by the device. + + + Battery level too low + R + Single + Optional + Boolean + + + Set to True if battery level is below or equal the low battery threshold. Set to False otherwise. + + + Battery shutdown + RW + Single + Optional + Boolean + + + Indicates that the device has shut down due to battery discharge. Can be reset by central application. + + + Number of cycles + R + Single + Optional + Integer + + + Number of times the battery has discharged and recharged. + + + Supply loss + R + Single + Optional + Boolean + + + Applies for battery devices that are also connected to mains supply. Set to True when mains power is lost. Set to False when mains power is back. + + + Supply loss counter + R + Single + Optional + Integer + + + Number of supply losses since last reset. + + + Supply loss counter reset + E + Single + Optional + + + + Reset the supply loss counter. + + + Supply loss reason + R + Single + Optional + String + + + Reason identified by the device why the device has lost mains supply (e.g. lightning if the device measured a high voltage, accident if the device identified a move with an accelerometer). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3412.xml b/application/src/main/data/lwm2m-registry/3412.xml new file mode 100644 index 0000000000..c3675639da --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3412.xml @@ -0,0 +1,288 @@ + + + + + + + LPWAN Communication + The uCIFI communication object provides attributes related to the communication on the LPWAN network, including multicast grouping. + 3412 + urn:oma:lwm2m:ext:3412 + 1.0 + 1.0 + Multiple + Optional + + + Type of network + R + Single + Optional + String + + + Type of LPWAN communication network (e.g. LoRaWAN, NB-IoT, wireless mesh, power line). + + + IPv4 address + RW + Multiple + Optional + String + + + Device’s IPv4 address. + + + IPv6 address + RW + Multiple + Optional + String + + + Device’s IPv6 address. + + + Network address + RW + Multiple + Optional + String + + + Address of the device on the LPWAN network. + + + Secondary network address + RW + Multiple + Optional + String + + + Secondary address used to communicate with the device on the LPWAN network. + + + MAC address + RW + Single + Mandatory + String + + + IEEE MAC address of the device. + + + Peer address + R + Multiple + Optional + String + + + Address of a peer (e.g. a router, a mesh node, a gateway). + + + Multicast group address + RW + Multiple + Optional + String + + + Group address from which the device should accept incoming messages and/or commands. + + + Multicast group key + RW + Multiple + Optional + String + + + Security key (e.g. AES128) to be shared with other members to be part of a multicast group. + + + Data rate + RW + Single + Optional + Integer + + B/s + Data rate of the communication used on the LPWAN network. + + + Transmit power + R + Single + Optional + Float + + dBm + Transmit power (also called TxPower) used by the device on the LPWAN network in decibel per milliwatt. + + + Frequency + RW + Single + Optional + Float + + Hz + Frequency of the wireless communication used on the LPWAN network. + + + Session time + RW + Single + Optional + Time + + + Starting time of the multicast session. The device shall not accept incoming messages before this time or after this time + session duration. + + + Session duration + R + Single + Optional + Time + + s + Duration of the multicast session. The device shall accept incoming messages only during this session duration time, starting at "Session time". + + + Mesh node + RW + Single + Optional + Boolean + + + Set to True if the device is a mesh node that should repeat incoming messages on a mesh network. + + + Maximum repeat time + RW + Single + Optional + Integer + + + Maximum number of times a message should be repeated within a mesh network. + + + Number of repeats + R + Single + Optional + Integer + + + Number of mesh nodes between the device and the destination device (e.g. another device or a router) including the destination node, on a mesh network. + + + Signal to noise ratio + R + Single + Optional + Float + + dB + Ratio of signal power to the noise power. + + + Communication failure + R + Single + Optional + Boolean + + + Set to True when the device can't communicate properly. + + + Received Signal Strength Indication + R + Single + Optional + Float + + dBm + Signal strength of the communication network measured by the device (a.k.a. RSSI or signal level). + + + IMSI + R + Single + Optional + String + + + Device’s International Mobile Subscriber Identity. + + + IMEI + R + Single + Optional + String + + + Device’s International Mobile Equipment Identity. + + + Current Communication Operator + R + Single + Optional + String + + + Device’s current communication operator. Communication operators could be private operators. Thus this resource is free text format and does not use Mobile Network Code or equivalent. + + + Integrated Circuit Card Identifier + R + Single + Optional + String + + + Unique identifier used to identify a communication SIM card. The ICCID is defined by the ITU-T recommendation E.118 as the Primary Account Number, composed of 19 or 20 characters containing the ISO Industry Identifier, country code, issuer identity, account ID, and other data which allows the network operator to identify the card. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3413.xml b/application/src/main/data/lwm2m-registry/3413.xml new file mode 100644 index 0000000000..e699cb67dd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3413.xml @@ -0,0 +1,108 @@ + + + + + + + uCIFI Generic Actuator + The uCIFI generic actuator may be used for any actuator but not to replace Outdoor Lamp Controllers nor for an Irrigation Valves which have a specific device type in the uCIFI data model. + 3413 + urn:oma:lwm2m:ext:3413 + 1.0 + 1.0 + Multiple + Optional + + + Default dimming level + RW + Single + Optional + Integer + 0..100 + /100 + Default dimming level that the generic actuator applies when the device is powered ON. + + + Dimming level + R + Single + Optional + Integer + 0..100 + /100 + Actual dimming level (0 for OFF and 100% for ON) measured on the actuator. + + + Command + RW + Single + Optional + Integer + 0..100 + /100 + Command (e.g. manual override, scheduler) sent to the actuator. + + + Command in action + R + Single + Optional + Integer + 0..100 + /100 + For devices connected on slow LPWAN networks, the command in action (this resource) may differ from a command that was sent (resource ID: 3). The command in action is the actual value of the command in action in the actuator. + + + Scheduler ID + RW + Multiple + Optional + Integer + + + The identifier of the schedulers that are assigned to the Command of this actuator. + + + Invalid scheduler + R + Single + Optional + Boolean + + + Set to True when one of the schedulers can’t be executed or is not supported by the actuator. Otherwise set to False. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3414.xml b/application/src/main/data/lwm2m-registry/3414.xml new file mode 100644 index 0000000000..b3f7ad4567 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3414.xml @@ -0,0 +1,108 @@ + + + + + + + uCIFI data bridge + The uCIFI data bridge object may be used to transport data from a 3rd party device, operating a proprietary network or protocol, over a LwM2M compliant network. + 3414 + urn:oma:lwm2m:ext:3414 + 1.0 + 1.0 + Multiple + Optional + + + Payload + RW + Single + Mandatory + String + + + Content of the message that is to be transported. + + + Hash + RW + Single + Optional + String + + + Hash to check the consistency of the data payload. Hash mechanism and calculation is vendor-specific. + + + Cumulated daily data volume up + R + Single + Optional + Integer + + B + Cumulated volume of data received by the device since beginning of the day. This information could be used for data invoicing. + + + Cumulated daily data volume down + R + Single + Optional + Integer + + B + Cumulated volume of data sent by the device since beginning of the day. This information could be used for data invoicing. + + + Cumulated daily data volume total + R + Single + Optional + Integer + + B + Cumulated volume of data sent and received by/from the device since beginning of the day. This information could be used for data invoicing. + + + Communication error + R + Single + Optional + Boolean + + + Set to True if the device detects a difference in a received payload and the hash. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3415.xml b/application/src/main/data/lwm2m-registry/3415.xml new file mode 100644 index 0000000000..5a76ca93a3 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3415.xml @@ -0,0 +1,98 @@ + + + + + + + Time synchronization + The uCIFI time synchronization object enables a device to sync-up its internal clock with a remote NTP server. + 3415 + urn:oma:lwm2m:ext:3415 + 1.0 + 1.0 + Single + Optional + + + NTP server address + RW + Single + Mandatory + String + + + Hostname or IP address of the Network Time Protocol server. + + + Backup NTP server address + RW + Single + Optional + String + + + Hostname or IP address of a backup NTP server to be used in case the main NTP server does not respond. + + + NTP period + RW + Single + Optional + String + + + Number of hours before which the device tries to reach the NTP server for time synchronization. + + + Last time sync + R + Single + Optional + Integer + + h + Last time at which a successful time synchronization occurred. + + + Time sync error + R + Single + Optional + Boolean + + + Set to True in case the latest time synchronization operation failed. Set to False in case the last operation succeeded. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3416.xml b/application/src/main/data/lwm2m-registry/3416.xml new file mode 100644 index 0000000000..bf9f23b6ac --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3416.xml @@ -0,0 +1,518 @@ + + + + + + + Outdoor lamp controller + The uCIFI outdoor lamp controller object provides attributes to control, command and monitor outdoor luminaires in streets, in tunnels, on roads and for illumination of park and gardens. + 3416 + urn:oma:lwm2m:ext:3416 + 1.0 + 1.0 + Multiple + Optional + + + Command + RW + Single + Mandatory + Integer + 0..100 + /100 + Command (e.g. manual override, scheduler) sent to the outdoor lamp controller. + + + Command in action + R + Single + Optional + Integer + 0..100 + /100 + For outdoor lighting applications, the command in action (this resource) may differ from a command that was sent (resource ID: 1), due to LPWAN network constraints and/or light adjustments within the lamp’s control gear (e.g. virtual power settings). The command in action is the actual value of the command in action in the outdoor lamp controller. + + + Dimming level + R + Single + Mandatory + Integer + 0..100 + /100 + Dimming level (0 for OFF and 100% for ON) measured on the outdoor lamp controller. + + + Default dimming level + RW + Single + Optional + Integer + 0..100 + /100 + The default dimming level that the outdoor lamp controller applies when the device is powered ON. + + + Lamp failure + R + Single + Optional + Boolean + + + Set to True if the outdoor lamp controller detects that the lamp is not producing light while it is expected to (e.g. the lamp is broken). + + + Lamp failure reason + R + Single + Optional + Integer + + + Description of the reason why the lamp failed (e.g. low power on a LED engine, no consumption detected while relay closed). + + + Control gear failure + R + Single + Optional + Boolean + + + Set to True in case the control gear has a failure. Outdoor lamp controllers may read the control gear failure from a DALI bus or by analyzing a 0-10 volts interface to the control gear. + + + Control gear failure reason + R + Single + Optional + Integer + + + Description of the reason why the control gear failed. You may refer to the DiiA list of possible control gear failures. + + + Relay failure + R + Single + Optional + Boolean + + + Set to True if the outdoor lamp controller detects that its relay is not operating as it is expected to. + + + Day burner + R + Single + Optional + Boolean + + + Set to True in case the lamp is ON while it should be OFF (e.g. day burner). + + + Cycling failure + R + Single + Optional + Boolean + + + Set to True if the outdoor lamp controller detects that the lamp is switching ON and OFF too often. + + + Control gear communication failure + R + Single + Optional + Boolean + + + Set to True in case the control gear (e.g. LED driver) does not answer on the DALI bus. + + + Scheduler ID + RW + Multiple + Optional + Integer + + + Identifier(s) of the scheduler(s) that are assigned to the Command of this outdoor lamp controller. + + + Invalid scheduler + R + Single + Optional + Boolean + + + Set to True when one of the schedulers can’t be executed or is not supported by the outdoor lamp controller. Otherwise set to False. + + + Lamp operating hours + R + Single + Optional + Integer + + h + Cumulated number of hours during which the lamp has been ON with a strictly positive dimming level. + + + Lamp operating hours reset + E + Single + Optional + + + + Reset of the lamp operating hours counter. + + + Lamp ON timestamp + R + Single + Optional + Time + + + Last date and time at which the lamp switched ON, i.e. from no light to light (e.g. power off to power on and/or dim level = 0 to dim level > 0). + + + Lamp switch counter + R + Single + Optional + Integer + + + Number of times the lamp was switched from ON to OFF since the last lamp switch counter reset. + + + Lamp switch counter reset + E + Single + Optional + + + + Reset the lamp switch counter. + + + Control gear start counter + R + Single + Optional + Integer + + + Number of times the control gear was switched from ON to OFF. + + + Control gear temperature + R + Single + Optional + Float + + Cel + Temperature measured by the control gear and transmitted to the device through DALI, Zhaga D4i or equivalent. + + + Control gear thermal derating + R + Single + Optional + Boolean + + + Set to True if the control gear has derated the light source due to high temperature. + + + Control gear thermal derating counter + R + Single + Optional + Integer + + + Number of time the control gear has derated the light source due to a high temperature, since last counter reset. + + + Control gear thermal derating counter reset + E + Single + Optional + + + + Reset of the control gear thermal derating counter. + + + Control gear thermal shutdown + R + Single + Optional + Boolean + + + Set to True if the control gear has shut the light source down due to high temperature. + + + Control gear thermal shutdown counter + R + Single + Optional + Integer + + + Number of times the control gear has shutdown the light source since last counter reset. + + + Control gear thermal derating counter reset + E + Single + Optional + + + + Reset of the control gear shutdown counter. + + + Output port + RW + Single + Optional + Integer + + + Address or reference of the output port (e.g. DALI port address or 1-10 volt output) in case of multiple control gears. + + + Standby mode + RW + Single + Optional + Boolean + + + Set to True if the outdoor lamp controller should keep its relay closed (control gear is powered ON) when command and/or dimming level is equal to 0. Set to False if the outdoor lamp controller should open its relay (control gear is not powered ON) when command and/or dimming level is equal to 0. + + + Constant light output + RW + Single + Optional + Boolean + + + Set to True to activate Constant Light Output dimming correction on the outdoor lamp controller. Set to False to deactivate Constant Light Output. + + + Cleaning factor enabled + RW + Single + Optional + Boolean + + + Light output of a luminaire may depend on the lamp cleaning factor. Cleaning factor may be used as a light output compensation. Set to True to activate lamp cleaning correction on the outdoor lamp controller. Set to False to deactivate the lamp cleaning correction. + + + Cleaning period + RW + Single + Optional + Integer + + + Number of days after which cleaning factor is back to 100%. + + + Initial lamp cleaning factor + RW + Single + Optional + Integer + 0..100 + /100 + Initial lamp cleaning correction factor to multiply to the command when the luminaire is cleaned, at the lamp cleaning date. + + + Lamp cleaning date + RW + Single + Optional + Time + + + Date at which the luminaire was last (or will be next) cleaned and the lamp cleaning factor should be set to the initial lamp cleaning factor. + + + Control type + RW + Single + Optional + Integer + 0..13 + + Type of control system with whioch the outdoor lamp controller switches, dims and monitors the lamp. The possible control types are: 0: No dimming control, 1 : DALI part 201 - Device Type 0, 2 : DALI part 202 - Device Type 1, 3 : DALI part 203 - Device Type 2, 4 : DALI part 204 - Device Type 3, 5 : DALI part 205 - Device Type 4, 6 : DALI part 206 - Device Type 5, 7 : DALI part 207 - Device Type 6, 8 : DALI part 208 - Device Type 7, 9 : DALI part 209 - Device Type 8, 10 : 0-10V, 11 : PWM, 12 : PWM_N, 13 : Other + + + Nominal Lamp wattage + RW + Single + Optional + Integer + + W + Active power of the light source at 100% dimming level. + + + Minimum dimming level + RW + Single + Optional + Integer + + /100 + Minimum dimming level under which the lamp will most probably not operate. + + + Minimum lamp wattage + RW + Single + Optional + Integer + + W + Expected active power of the light source at its minimum dimming level. This value may be used to detect failure at low dimming level. + + + Light color temperature command + RW + Single + Optional + String + + K + Latest light color temperature command sent to the lamp actuator. + + + Actual light color temperature + R + Single + Optional + String + + K + The actual light color temperature of the light source. + + + Virtual power output + RW + Single + Optional + Integer + 0..100 + /100 + Percentage of nominal power at which the light source should be set when the Command is set to 100%. + + + Voltage at max dim level + RW + Single + Optional + Float + + V + Voltage level on the control port that corresponds to maximum dimming level. This applies only if Control Type = 0-10V. + + + Voltage at min dim level + RW + Single + Optional + Float + + V + Voltage level on the control port that corresponds to minimum dimming level. This applies only if Control Type = 0-10V. + + + Light source voltage + R + Single + Optional + Float + + V + Voltage (usually DC voltage) to the light source or generic load, measured at the output of the control gear. + + + Light source current + R + Single + Optional + Float + + A + Current (usually DC current) delivered by the control gear to the light source or generic load, measured at the output of the control gear. + + + Light source active power + R + Single + Optional + Float + + W + Active power of the light source or generic load, measured at the output of the control gear. + + + Light source active energy + R + Single + Optional + Float + + kWh + Cumulated active energy consumption of the light source or generic load, measured at the output of the control gear. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3417.xml b/application/src/main/data/lwm2m-registry/3417.xml new file mode 100644 index 0000000000..d22a318d84 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3417.xml @@ -0,0 +1,188 @@ + + + + + + + Luminaire asset + The uCIFI luminaire asset is an object that enables outdoor lighting control software to configure outdoor lamp controllers. It also enables outdoor lamp controllers to send Zhaga D4i or DALI attributes read from the control gears, to centrally managed outdoor lighting control software or asset management systems. + 3417 + urn:oma:lwm2m:ext:3417 + 1.0 + 1.0 + Multiple + Optional + + + Asset GTIN + R + Single + Mandatory + String + + + Global trade-in international number used in the Luminaire Asset controlled by the device. + + + Year of manufacture + RW + Single + Optional + Integer + + + Year of manufacture of the luminaire. + + + Week of manufacture + RW + Single + Optional + Integer + + + Week of manufacture of the luminaire. + + + Nominal light output + RW + Single + Optional + Integer + + lm + Nominal light output of the luminaire. + + + Light distribution type + RW + Single + Optional + Integer + + + Enumeration of possible light distribution type, using the Zhaga D4i enumeration. Please refer to ZD4i standard for more details. + + + Luminaire color + RW + Single + Optional + String + + + Painting color of the luminaire. + + + Nominal input power + R + Single + Optional + Float + + W + Nominal input power of the luminaire. + + + Power at minimum dim level + R + Single + Optional + Float + + W + Power at minimum dim level for the luminaire. + + + Nominal max AC mains voltage + R + Single + Optional + Integer + + V + Nominal max AC mains voltage for the luminaire to operate. + + + Nominal min AC mains voltage + R + Single + Optional + Integer + + V + Nominal min AC mains voltage for the luminaire to operate. + + + CRI + R + Single + Optional + Integer + 0..100 + + Color rendering index (0 to 100) of the luminaire. + + + CCT value + R + Single + Optional + Integer + + K + Color temperature of the luminaire. + + + Luminaire identification + R + Single + Optional + String + + + Luminaire identification as per DiiA/D4i specification part 251 (MB1 extension): 60 ascii character string. + + + Luminaire identification number + R + Single + Optional + String + + + Luminaire identification number as per DiiA/D4i specification part 251 (MB1 extension): 20 digit number. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3418.xml b/application/src/main/data/lwm2m-registry/3418.xml new file mode 100644 index 0000000000..6ff07dfa94 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3418.xml @@ -0,0 +1,348 @@ + + + + + + + Electrical monitor + The uCIFI electrical monitor object provides attributes related to the analysis of electrical consumption in an outdoor luminaire or in a streetlight cabinet. It also identifies electrical anomalies. + 3418 + urn:oma:lwm2m:ext:3418 + 1.0 + 1.0 + Multiple + Optional + + + Supply voltage + R + Single + Optional + Float + + V + Electrical voltage supplied to the device. + + + Supply current + R + Single + Optional + Float + + A + Electrical current supplied to the device. + + + Frequency + R + Single + Optional + Float + + Hz + Frequency of the supply current to the device. + + + Active power + R + Single + Optional + Float + + W + Active power consumed by the device and its electrical load. + + + Power factor + R + Single + Optional + Float + -1..1 + + Power factor is equal to active power divided by apparent power. The value is between -1 and +1. + + + Cumulated active energy + R + Single + Optional + Float + + kWh + Cumulated number of kWh measured by the device and its load since last energy counter reset. + + + Energy reset + E + Single + Optional + + + + Reset both cumulated active and reactive energy counter. + + + Low power factor threshold + RW + Single + Optional + Float + + + Threshold below which the device should trigger a low power factor event. + + + Low power factor + R + Single + Optional + Boolean + + + Set to True if the power factor is below threshold. This is an absolute value threshold. + + + Low power threshold + RW + Single + Optional + Float + + W + Threshold below which the device should trigger a low power event. + + + Low power threshold at low dim level + RW + Single + Optional + Float + + W + Threshold below which the device should trigger a low power event. This is an addition to the low power threshold to allow a separated threshold when dim level is low. + + + Low power + R + Single + Optional + Boolean + + + Set to True if the power is below threshold. Vendor may consider the Low power threshold at low dim level to set this resource to True, taking into account the lamp dim level. + + + High power threshold + RW + Single + Optional + Float + + W + Threshold above which the device should trigger a high power event. + + + High power threshold at low dim level + RW + Single + Optional + Float + + W + Threshold above which the device should trigger a high power event. This is an addition to the high power threshold to allow a separated threshold when dim level is low. + + + High power + R + Single + Optional + Boolean + + + Set to True if the power is above threshold. Vendor may consider the High power threshold at low dim level to set this resource to True, taking into account the lamp dim level. + + + Low current threshold + RW + Single + Optional + Float + + A + Threshold below which the device should trigger a low current event. + + + Low current + R + Single + Optional + Boolean + + + Set to True if the current is below threshold. + + + High current threshold + RW + Single + Optional + Float + + A + Threshold above which the device should trigger a high current event. + + + High current + R + Single + Optional + Boolean + + + Set to True if the current is above threshold. + + + Low voltage threshold + RW + Single + Optional + Float + + V + Threshold below which the device should trigger a low voltage event. + + + Low voltage + R + Single + Optional + Boolean + + + Set to True if the voltage is below threshold. + + + High voltage threshold + RW + Single + Optional + Float + ON;OFF + V + Threshold above which the device should trigger a high voltage event. + + + High voltage + R + Single + Optional + Boolean + + + Set to True if the voltage is above threshold. + + + Critical inrush threshold + RW + Single + Optional + Float + + A + Threshold above which the device should trigger a critical inrush event. + + + Critical inrush current + R + Single + Optional + Boolean + + + Set to True if the inrush current is above threshold. + + + Minimum inrush Current + R + Single + Optional + Float + + A + Minimum inrush current since last configuration. + + + Maximum inrush Current + R + Single + Optional + Float + + A + Maximum inrush current since last configuration. + + + Latest inrush Current + R + Single + Optional + Float + + A + Latest inrush current measured since last time the relay switched ON. + + + Reactive power + R + Single + Optional + Float + + var + Instantaneous reactive power measured by the device and its electrical load. + + + Reactive energy + R + Single + Optional + Float + + varh + Cumulative reactive power measured by the device and its electrical load since last energy counter reset. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3419.xml b/application/src/main/data/lwm2m-registry/3419.xml new file mode 100644 index 0000000000..88b66119a7 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3419.xml @@ -0,0 +1,78 @@ + + + + + + + Photocell + A uCIFI photocell object is used for lighting control, particularly to identify when light should be switched ON/OFF. + 3419 + urn:oma:lwm2m:ext:3419 + 1.0 + 1.0 + Multiple + Optional + + + ON lux level + RW + Single + Optional + Float + + lx + Lux level below which the photocell switches its relay ON or sends a switch ON message on the network. + + + OFF lux level + RW + Single + Optional + Float + + lx + Lux level above which the photocell switches its relay OFF or sends a switch OFF message on the network. + + + Photocell status + R + Single + Optional + Boolean + + + Set to True if lux level is below ON lux level. Set to False if lux level is above OFF lux. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3420.xml b/application/src/main/data/lwm2m-registry/3420.xml new file mode 100644 index 0000000000..835f4ec8a5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3420.xml @@ -0,0 +1,58 @@ + + + + + + + LED color light + The uCIFI LED color light enables to control light colors of a LED luminaire. + 3420 + urn:oma:lwm2m:ext:3420 + 1.0 + 1.0 + Multiple + Optional + + + RGB value + RW + Single + Mandatory + String + + + Text string according to the RBG hexadecimal format with # (e.g. #FF0000 for 100% red). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3421.xml b/application/src/main/data/lwm2m-registry/3421.xml new file mode 100644 index 0000000000..dadf8d8f47 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3421.xml @@ -0,0 +1,618 @@ + + + + + + + Single-phase electrical meter + The uCIFI single-phase electrical meter measures the electrical consumption of loads on one electrical line, such as electrical cabinets in city infrastructures, street lighting networks, homes and buildings. + 3421 + urn:oma:lwm2m:ext:3421 + 1.0 + 1.0 + Multiple + Optional + + + Voltage + R + Single + Mandatory + Float + + V + Instantaneous voltage measured between line and neutral. + + + Low voltage threshold + R + Single + Optional + Float + + V + Set to True when voltage is below low voltage threshold. + + + Low voltage + R + Single + Optional + Boolean + + + Set to True when voltage is below low voltage threshold. + + + High voltage threshold + R + Single + Optional + Float + + V + Threshold above which the high voltage resource is set to True. + + + High voltage + R + Single + Optional + Boolean + + + Set to True when voltage is above high voltage threshold. + + + Current + R + Single + Mandatory + Float + + A + Instantaneous current measured. + + + Low current threshold + R + Single + Optional + Float + + A + Threshold below which the low current resource is set to True. + + + Low current + R + Single + Optional + Boolean + + + Set to True when current is below low current threshold. + + + High current threshold + R + Single + Optional + Float + + V + Threshold above which the high current resource is set to True. + + + High current + R + Single + Optional + Boolean + + + Set to True when current is above high current threshold. + + + Frequency + R + Single + Optional + Float + + Hz + Instantaneous frequency measured. + + + Angle of I-U + R + Single + Optional + Float + + deg + Instantaneous phase angle measured. + + + Instantaneous Power Factor + R + Single + Mandatory + Float + + + Instantaneous power factor overall. + + + Low power factor threshold + R + Single + Optional + Float + + + Threshold below which the low power factor resource is set to True. + + + Low power factor + R + Single + Optional + Boolean + + + Set to True when power factor is below low power factor threshold. + + + Active power+ (QI+QIV) + R + Single + Mandatory + Float + + W + Active power import + + + Active power- (QII+QIII) + R + Single + Optional + Float + + W + Active power export. + + + Reactive power+ (QI+QII) + R + Single + Optional + Float + + var + Reactive power import. + + + Reactive power- (QIII+QIV) + R + Single + Optional + Float + + var + Reactive power export + + + Instantaneous Apparent Power import (+VA) + R + Single + Optional + Float + + VA + Instantaneous apparent power import. + + + Instantaneous Apparent Power export (-VA) + R + Single + Optional + Float + + VA + Instantaneous apparent power export. + + + Instantaneous Active Power (|+A|+|-A|) + R + Single + Optional + Float + + W + Instantaneous active power. + + + Instantaneous Net Active Power (|+A|-|-A|) + R + Single + Optional + Float + + W + Instantaneous net active power. + + + Measurement period of Instantaneous value + R + Single + Optional + Float + + s + Measurement period of instantaneous value. + + + Active energy import (+A) Un + R + Single + Mandatory + Float + + Wh + Active energy import (unified rate). + + + Active energy export (-A) Un + R + Single + Optional + Float + + Wh + Active energy export (unified rate). + + + Active energy (|+A|+|-A|) Combined total + R + Single + Optional + Float + + Wh + Active energy (|+A|+|-A|) Combined total. + + + Active energy (|+A|-|-A|) Combined total + R + Single + Optional + Float + + Wh + Active energy (|+A|-|-A|) Combined total. + + + Reactive energy import (+R) Un + R + Single + Optional + Float + + varh + Reactive energy import (unified rate) + + + Reactive energy export (-R) Un + R + Single + Optional + Float + + varh + Reactive energy export (unified rate). + + + Reactive energy QI (+Ri) Un + R + Single + Optional + Float + + varh + Reactive energy QI (+Ri) (unified rate). + + + Reactive energy QII (+Rc) Un + R + Single + Optional + Float + + varh + Reactive energy QII (+Rc) (unified rate). + + + Reactive energy QIII (-Ri) Un + R + Single + Optional + Float + + varh + Reactive energy QIII (-Ri) (unified rate). + + + Reactive energy QIV (-Rc) Un + R + Single + Optional + Float + + varh + Reactive energy QIV (-Rc) (unified rate). + + + Apparent energy import Un + R + Single + Optional + Float + + VAh + Apparent energy import (unified rate). + + + Apparent energy export Un + R + Single + Optional + Float + + VAh + Apparent energy export (unified rate). + + + Number of power failures + R + Single + Optional + Float + + + Number of power failures. + + + Number of long power failures + R + Single + Optional + Float + + + Number of long power failures. + + + Time threshold for long power failure + RW + Single + Optional + Float + + s + Time threshold for long power failure. + + + Duration of last long power failure + R + Single + Optional + Float + + s + Duration of last long power failure. + + + Threshold for voltage sag + RW + Single + Optional + Float + + V + Threshold for voltage sag. + + + Time threshold for voltage sag + RW + Single + Optional + Float + + s + Time threshold for voltage sag. + + + Number of voltage sags + R + Single + Optional + Float + + + Number of voltage sags. + + + Duration of last voltage sag + R + Single + Optional + Float + + s + Duration of last voltage sag. + + + Magnitude of last voltage sag + R + Single + Optional + Float + + V + Magnitude of last voltage sag. + + + Threshold for voltage swell + RW + Single + Optional + Float + + V + Threshold for voltage sag. + + + Time threshold for voltage swell + RW + Single + Optional + Float + + s + Time threshold for voltage sag. + + + Number of voltage swells + R + Single + Optional + Float + + + Number of voltage sags. + + + Duration of last voltage swell + R + Single + Optional + Float + + s + Duration of last voltage sag. + + + Magnitude of last voltage swell + R + Single + Optional + Float + + V + Magnitude of last voltage swell. + + + Threshold for missing voltage (voltage cut) + RW + Single + Optional + Float + + V + Threshold for missing voltage (voltage cut). + + + Time threshold for voltage cut + RW + Single + Optional + Float + + s + Time threshold for voltage cut. + + + Voltage cut + R + Single + Optional + Boolean + + + Set to True if a voltage cut for a time above the time threshold and for a voltage below the voltage threshold is detected. + + + CT Numerator Parameter + RW + Single + Optional + Float + + + Transformer ratio - current (numerator). + + + CT Denominator Parameter + RW + Single + Optional + Float + + + Transformer ratio - voltage (denominator). + + + VT Numerator Parameter + RW + Single + Optional + Float + + + Transformer ratio - current (numerator). + + + VT Denominator Parameter + RW + Single + Optional + Float + + + Transformer ratio - voltage (denominator). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3423.xml b/application/src/main/data/lwm2m-registry/3423.xml new file mode 100644 index 0000000000..6e0aaf3b21 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3423.xml @@ -0,0 +1,158 @@ + + + + + + + Gas meter + The uCIFI gas meter measures the volume of gas that was distributed through the gas meter, in pulse count and/or in m3. It also detects anomalies in the gas meter. + 3423 + urn:oma:lwm2m:ext:3423 + 1.0 + 1.0 + Multiple + Optional + + + Cumulated gas volume + R + Single + Mandatory + Float + + m3 + Number of cubic meters of gas distributed through the meter since last reset. + + + Cumulated gas meter value reset + E + Single + Optional + + + + Reset the cumulated meter value. + + + Type of meter + RW + Single + Optional + String + + + Type of gas meter. + + + Cumulated pulse value + R + Single + Optional + Integer + + + Cumulated number of pulses detected on the gas meter. + + + Cumulated pulse value reset + E + Single + Optional + + + + Reset the cumulated pulse value. + + + Pulse ratio + RW + Single + Optional + Integer + + + Ratio to calculate gas volume from pulse value. + + + Minimum flow rate + R + Single + Optional + Float + + m3/s + Minimum flow rate since last metering value. + + + Maximum flow rate + R + Single + Optional + Float + + m3/s + Maximum flow rate since last metering value. + + + Leak suspected + R + Single + Optional + Boolean + + + Set to True if gas leak is suspected. + + + Leak detected + R + Single + Optional + Boolean + + + Set to True if gas leak is detected. + + + High temperature + R + Single + Optional + Boolean + + + Set to True if high temperature is detected around the gas meter. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3424.xml b/application/src/main/data/lwm2m-registry/3424.xml new file mode 100644 index 0000000000..01df43cbaf --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3424.xml @@ -0,0 +1,178 @@ + + + + + + + Water meter + The uCIFI water meter measures water volume that was distributed through a water meter, in pulse count as well as in m3. It also detects anomalies in the water meter. + 3424 + urn:oma:lwm2m:ext:3424 + 1.0 + 1.0 + Multiple + Optional + + + Cumulated water volume + R + Single + Mandatory + Float + + m3 + Number of cubic meters of water distributed through the meter since last reset. + + + Cumulated water meter value reset + E + Single + Optional + + + + Reset the cumulated meter value. + + + Type of meter + RW + Single + Optional + String + + + Type of water meter. + + + Cumulated pulse value + R + Single + Optional + Integer + + + Cumulated number of pulses detected on the meter. + + + Cumulated pulse value reset + E + Single + Optional + + + + Reset the cumulated pulse value. + + + Pulse ratio + RW + Single + Optional + Integer + + + Ratio to calculate water volume from pulse value. + + + Minimum flow rate + R + Single + Optional + Float + + m3/s + Minimum flow rate since last metering value. + + + Maximum flow rate + R + Single + Optional + Float + + m3/s + Maximum flow rate since last metering value. + + + Leak suspected + R + Single + Optional + Boolean + + + Set to True if water leak is suspected. + + + Leak detected + R + Single + Optional + Boolean + + + Set to True if leak is detected. + + + Back flow detected + R + Single + Optional + Boolean + + + Set to True if water back flow is detected. + + + Blocked meter + R + Single + Optional + Boolean + + + Set to True if water meter is blocked. + + + Fraud detected + R + Single + Optional + Boolean + + + Set to True if fraud is detected. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3425.xml b/application/src/main/data/lwm2m-registry/3425.xml new file mode 100644 index 0000000000..dd94f14dfd --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3425.xml @@ -0,0 +1,108 @@ + + + + + + + Irrigation valve + The uCIFI irrigation valve provides a way to control and monitor an irrigation valve. + 3425 + urn:oma:lwm2m:ext:3425 + 1.0 + 1.0 + Multiple + Optional + + + Default status + RW + Single + Optional + Integer + 0..100 + /100 + Default status (level of opening of the valve from 0 to 100%) that is applied when the device is powered ON. + + + Status + R + Single + Optional + Integer + 0..100 + /100 + Actual level of opening of the valve (from 0 to 100%). + + + Command + RW + Single + Optional + Integer + 0..100 + /100 + Command (e.g. manual override, scheduler) sent to the irrigation valve. + + + Command in action + R + Single + Optional + Integer + 0..100 + /100 + For devices connected on slow LPWAN networks, the command in action (this resource) may differ from a command that was sent (resource ID: 3). The command in action is the actual value of the command in action on the irrigation valve. + + + Scheduler ID + RW + Multiple + Optional + Integer + + + Identifiers of the schedulers that are assigned to the Command of this irrigation valve. + + + Invalid scheduler + R + Single + Optional + Boolean + + + Set to True when one of the schedulers can’t be executed or is not supported by this irrigation valve. Otherwise set to False. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3426.xml b/application/src/main/data/lwm2m-registry/3426.xml new file mode 100644 index 0000000000..7f33c1bc1a --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3426.xml @@ -0,0 +1,188 @@ + + + + + + + Water quality sensor + The uCIFI water quality sensor measures the quality of the water in the drinkable water distribution network, in water tanks or in lakes and rivers. + 3426 + urn:oma:lwm2m:ext:3426 + 1.0 + 1.0 + Multiple + Optional + + + pH + R + Single + Optional + Float + + + Current or last value of the pH measured by the sensor. + + + Chlorine + R + Single + Optional + Float + + ppm + Current or last value of the chlorine measured by the sensor. + + + Redox or ORP + R + Single + Optional + Float + + V + Current or last value of the oxidation reduction potential measured by the sensor. + + + Total dissolved gas or TDG + R + Single + Optional + Float + + ppm + Current or last value of the dissolved gas measured by the sensor. + + + Dissolved oxygen + R + Single + Optional + Float + + ppm + Current or last value of the dissolved oxygen measured by the sensor. + + + Turbidity + R + Single + Optional + Float + + NTU + Current or last value of the turbidity measured by the sensor using the Nephelometric Turbidity Unit (NTU). + + + Conductivity + R + Single + Optional + Float + + S/m + Current or last value of the conductivity measured by the sensor. + + + Conductance + R + Single + Optional + Float + + S/m + Current or last value of the conductance measured by the sensor. + + + Total suspended solids + R + Single + Optional + Float + + mg/l + Current or last value of the TSS measured by the sensor. + + + Total dissolved solids + R + Single + Optional + Float + + mg/l + Current or last value of the TDS measured by the sensor. + + + Salinity + R + Single + Optional + Float + + ppt + Current or last value of the salinity measured by the sensor. + + + NO3 + R + Single + Optional + Float + + mg/l + Current or last value of NO3 measured by the sensor. + + + NH3 + R + Single + Optional + Float + + mg/l + Current or last value of NH3 measured by the sensor. + + + NH4 + R + Single + Optional + Float + + mg/l + Current or last value of NH4 measured by the sensor. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3427.xml b/application/src/main/data/lwm2m-registry/3427.xml new file mode 100644 index 0000000000..4ffe60c2bc --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3427.xml @@ -0,0 +1,138 @@ + + + + + + + Pressure monitoring sensor + The uCIFI water pressure monitoring sensor measures water pressure and help identify water leaks in water distribution pipelines. + 3427 + urn:oma:lwm2m:ext:3427 + 1.0 + 1.0 + Multiple + Optional + + + Pressure + R + Single + Mandatory + Float + + Pa + Last or current pressure value measured by the sensor. + + + Sampling period + RW + Single + Optional + Integer + + s + Number of seconds between two measurement. + + + Leak detected + R + Single + Optional + Boolean + + + Set to True if a water leak is detected by the sensor. + + + Hammer effect detected + R + Single + Optional + Boolean + + + Set to True if hammer effect is detected in the water pipeline. + + + Minimum measured pressure value + R + Single + Optional + Float + + Pa + Minimum value measured by the sensor since power ON or reset. + + + Maximum measured pressure value + R + Single + Optional + Float + + Pa + Maximum value measured by the sensor since power ON or reset. + + + Minimum range pressure value + R + Single + Optional + Float + + Pa + Minimum value that can be measured by the sensor. + + + Maximum range pressure value + R + Single + Optional + Float + + Pa + Maximum value that can be measured by the sensor. + + + Reset min and max measured pressure values + E + Single + Optional + + + + Set the minimum and maximum measured values to current value. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3428.xml b/application/src/main/data/lwm2m-registry/3428.xml new file mode 100644 index 0000000000..68dab08269 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3428.xml @@ -0,0 +1,268 @@ + + + + + + + Air quality + The uCIFI air quality sensor reports measurement required to calculate Air Quality Index (AIQ. It also provides resources for average calculation as per the IQ index calculation. + 3428 + urn:oma:lwm2m:ext:3428 + 1.0 + 1.0 + Multiple + Optional + + + PM10 + R + Single + Optional + Float + + ug/m3 + Level of PM10 measured by the air quality sensor. + + + PM10 24 hour average + R + Single + Optional + Float + + ug/m3 + Average level of PM10 measured by the sensor during the last 24 hours. + + + PM2.5 + R + Single + Optional + Float + + ug/m3 + Level of PM2.5 measured by the air quality sensor. + + + PM2.5 24 hour average + R + Single + Optional + Float + + ug/m3 + Average level of PM2.5 measured by the sensor during the last 24 hours. + + + PM1 + R + Single + Optional + Float + + ug/m3 + Level of PM1 measured by the air quality sensor. + + + PM1 24 hour average + R + Single + Optional + Float + + ug/m3 + Average level of PM1 measured by the sensor during the last 24 hours. + + + CO + R + Single + Optional + Float + + ppm + Level of carbon monoxide measured by the air quality sensor. + + + CO 8 hour average + R + Single + Optional + Float + + ppm + Average level of carbon monoxide measured by the sensor during the last 8 hours. + + + SO2 + R + Single + Optional + Float + + ppm + Level of sulfur dioxide measured by the air quality sensor. + + + SO2 1 hour average + R + Single + Optional + Float + + ppm + Average level of sulfur dioxide measured by the sensor during the last 1 hour. + + + SO2 24 hour average + R + Single + Optional + Float + + ppm + Average level of sulfur dioxide measured by the sensor during the last 24 hours. + + + O3 + R + Single + Optional + Float + + ppm + Level of ozone measured by the air quality sensor. + + + O3 1 hour average + R + Single + Optional + Float + + ppm + Average level of ozone measured by the sensor during the last 1 hour. + + + O3 8 hour average + R + Single + Optional + Float + + ppm + Average level of ozone measured by the sensor during the last 8 hour. + + + NO2 + R + Single + Optional + Float + + ppm + Level of nitrogen dioxide measured by the air quality sensor. + + + NO2 1 hour average + R + Single + Optional + Float + + ppm + Average level of nitrogen dioxide measured by the sensor during the last 1 hour. + + + CO2 + R + Single + Optional + Float + + ppm + Level of carbon dioxide measured by the air quality sensor. + + + CO2 1 hour average + R + Single + Optional + Float + + ppm + Average level of carbon dioxide measured by the sensor during the last 1 hour. + + + NO + R + Single + Optional + Float + + ppm + Level of nitric oxide measured by the air quality sensor. + + + NO 1 hour average + R + Single + Optional + Float + + ppm + Average level of nitric oxide measured by the sensor during the last 1 hour. + + + H2S + R + Single + Optional + Float + + ppm + Level of hydrogen sulfid measured by the air quality sensor. + + + H2S 1 hour average + R + Single + Optional + Float + + ppm + Average level of hydrogen sulfid measured by the sensor during the last 1 hour. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3429.xml b/application/src/main/data/lwm2m-registry/3429.xml new file mode 100644 index 0000000000..e5f6f46b75 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3429.xml @@ -0,0 +1,108 @@ + + + + + + + Tilt sensor + The uCIFI tilt sensor provides the angle between the object's internal vertical and the perpendicular to the earth. + 3429 + urn:oma:lwm2m:ext:3429 + 1.0 + 1.0 + Multiple + Optional + + + Angle + R + Single + Mandatory + Float + + rad + Angle between the internal vertical line and the perpendicular to earth. + + + Minimum measured Angle + R + Single + Optional + Float + + rad + Minimum angle measured by the tilt sensor since last reset or since power ON if no reset. + + + Maximum measured angle + R + Single + Optional + Float + + rad + Maximum angle measured by the tilt sensor since last reset or since power ON if no reset. + + + Reset min max angles + E + Single + Optional + + + + Set the minimum and maximum measured angles to the current angle value. + + + Out of position threshold + RW + Single + Optional + Float + + rad + The angle above which the device triggers an out of position event. + + + Out of position + R + Single + Optional + Boolean + + + Set to True if the angle is above the out of position threshold. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3430.xml b/application/src/main/data/lwm2m-registry/3430.xml new file mode 100644 index 0000000000..76a14c0656 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3430.xml @@ -0,0 +1,371 @@ + + + + + + + Global Navigation Satellite System + More than a positioning object, the uCIFI global navigation satellite system object provides all the information required to calculate a position/location. + 3430 + urn:oma:lwm2m:ext:3430 + 1.0 + 1.0 + Single + Optional + + + Fix timestamp + R + Single + Mandatory + Time + + s + Timestamp of when the location measurement was performed. + + + Latitude + R + Single + Mandatory + Float + + lat + Decimal notation of latitude, e.g. -43.5723 [World Geodetic System 1984]. This value ranges from [-90, 90]. + + + Longitude + R + Single + Mandatory + Float + + lon + Decimal notation of longitude, e.g. 153.21760 [World Geodetic System 1984]. This value ranges from [-180, 180]. + + + Altitude + R + Single + Optional + Float + + m + Altitude above mean sea level in meters. + + + Speed + R + Single + Optional + Integer + + m/s + Horizontal speed calculated by the device. + + + Heading + R + Single + Optional + Float + + deg + Direction that the device is following + + + Radius + R + Single + Optional + Float + + m + Radius of a circular area corresponding to the location’s uncertainty (GPS data precision). Negative values indicate that the radius is not available. + + + HDOP + R + Single + Optional + Float + + + Horizontal dilution of precision. + + + VDOP + R + Single + Optional + Float + + + Vertical dilution of precision. + + + Estimated horizontal accuracy + R + Single + Optional + Float + + m + Estimated horizontal accuracy. + + + Estimated vertical accuracy + R + Single + Optional + Float + + m + Estimated vertical accuracy. + + + Estimated speed accuracy + R + Single + Optional + Float + + m/s + Estimated speed accuracy. + + + Estimated heading accuracy + R + Single + Optional + Float + + deg + Estimated heading accuracy. + + + Fix type + R + Single + Optional + Integer + + + + + + + Fix dimension + R + Single + Optional + Integer + + + + + + + Used satellites + R + Single + Optional + Integer + + + Number of satellites used for the fix. + + + Visible satellites + R + Single + Optional + Integer + + + Number of satellites viewed. Represent all the satellites seen but some of them cannot be in use (e.g. bad signal). + + + Satellite identifiers + R + Multiple + Optional + String + + + Identifier of the satellite. + + + Satellite identifiers + R + Multiple + Optional + Float + + deg + Elevation of the satellite. + + + Satellite azimuth + R + Multiple + Optional + Float + + deg + Azimuth of the satellite. + + + Almanac + R + Single + Optional + Boolean + + + + + + + Ephemeris + R + Multiple + Optional + Boolean + + + + + + + Signal-to-noise ratio + R + Multiple + Optional + Float + + dB + Strength of the signal for each satellite, also called carrier-to-noise. + + + GNSS + R + Multiple + Optional + Integer + + + + + + + Hardware RTC + R + Single + Optional + Time + + s + Time of the internal clock of the GNSS hardware. + + + Assisted GPS + RW + Single + Optional + Boolean + + + Set to True if the almanac is obtained via a cellular connection. Set to False otherwise. + + + Power command + RW + Single + Optional + Boolean + + + Command to switch the hardware ON or OFF and status of the device. + + + PDOP + R + Single + Optional + Float + + + Dilution of precision (NMEA-0183 GSA). + + + Status + R + Single + Mandatory + String + + + Status A=active or V=Void (NMEA-0183 RMC). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3431.xml b/application/src/main/data/lwm2m-registry/3431.xml new file mode 100644 index 0000000000..8f0ec363d1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3431.xml @@ -0,0 +1,98 @@ + + + + + + + Parking Sensor + The uCIFI parking sensor provides actual and cumulated occupancy duration as well as forbidden parking detection. + 3431 + urn:oma:lwm2m:ext:3431 + 1.0 + 1.0 + Multiple + Optional + + + Occupancy + R + Single + Mandatory + Boolean + + + Set to True if the parking place is occupied. Set to False if the parking place is free. + + + Duration + R + Single + Optional + Integer + + s + Number of seconds since the parking place is occupied. If not occupied, duration shows the duration of the last occupation. + + + Daily Duration + R + Single + Optional + Integer + + s + Cumulated occupation time since beginning of the day. + + + Forbidden parking detected + R + Single + Optional + Boolean + + + Set to True if the vehicle present on the parking place is not authorized. Set to False if parking place is free or if the vehicle is authorized. + + + Type of Sensor + RW + Single + Optional + String + + + Type of sensor (e.g. PIR, camera). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3432.xml b/application/src/main/data/lwm2m-registry/3432.xml new file mode 100644 index 0000000000..4816248320 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3432.xml @@ -0,0 +1,218 @@ + + + + + + + Traffic Counter + The uCIFI traffic counter provides vehicle counting and traffic analysis data. A traffic counting device may implement multiple instances of this traffic counter object, each of them being in charge of counting different categories of vehicles (e.g. bikes, cars, trucks). + 3432 + urn:oma:lwm2m:ext:3432 + 1.0 + 1.0 + Multiple + Optional + + + Cumulated number + R + Single + Optional + Integer + + + Cumulated number of vehicles counted since last reset. + + + Reset cumulated number + E + Single + Optional + + + + Reset the cumulated number of vehicles. + + + Cumulated number today + R + Single + Mandatory + Integer + + + Cumulated number of vehicles counted today. + + + Measuring period 1 + RW + Single + Optional + Integer + + s + Time period 1 during which the counter shall provide number of vehicles (e.g. 1 hour). + + + Measuring period 2 + RW + Single + Optional + Integer + + s + Time period 2 during which the counter shall provide number of vehicles (e.g. 15 minutes). + + + Measuring period 3 + RW + Single + Optional + Integer + + s + Time period 3 during which the counter shall provide number of vehicles (e.g. 5 minutes). + + + Cumulated number during last period 1 + R + Single + Optional + Integer + + + Cumulated number of vehicles counted during the last period 1 (e.g. 1 hour). + + + Cumulated number during last period 2 + R + Single + Optional + Integer + + + Cumulated number of vehicles counted during the last period 2 (e.g. 15 minutes). + + + Cumulated number during last period 3 + R + Single + Optional + Integer + + + Cumulated number of vehicles counted during the last period 3 (e.g. 5 minutes). + + + Average speed during last period 1 + R + Single + Optional + Integer + + + Average speed measured on the vehicles during the last period 1 (e.g. 1 hour). + + + Average speed during last period 2 + R + Single + Optional + Integer + + + Average speed measured on the vehicles during the last period 2 (e.g. 15 minutes). + + + Average speed during last period 3 + R + Single + Optional + Integer + + + Average speed measured on the vehicles during the last period 3 (e.g. 5 minutes). + + + Average distance during last period 1 + R + Single + Optional + Integer + + + Average distance between two vehicles measured during the last period 1 (e.g. 1 hour). + + + Average distance during last period 2 + R + Single + Optional + Integer + + + Average distance between two vehicles measured during the last period 2 (e.g. 15 minutes). + + + Average distance during last period 3 + R + Single + Optional + Integer + + + Average distance between two vehicles measured during the last period 3 (e.g. 5 minutes). + + + Speed limit threshold + RW + Single + Optional + Integer + + + Speed limit threshold used to calculate the percentage of vehicles above speed limit. + + + Percentage above speed limit + R + Single + Optional + Integer + + + Percentage of vehicles driving above speed limit. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3433.xml b/application/src/main/data/lwm2m-registry/3433.xml new file mode 100644 index 0000000000..018f2af954 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3433.xml @@ -0,0 +1,128 @@ + + + + + + + Noise sensor + The uCIFI noise sensor reports a noise measurement in decibel. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the noise sensor and some specific alarms resulting from noise analysis by the device. + 3433 + urn:oma:lwm2m:ext:3433 + 1.0 + 1.0 + Multiple + Optional + + + Gunshot detected + R + Single + Optional + Boolean + + + Set to True when a gunshot is detected. + + + Abnormal noise detected + R + Single + Optional + Boolean + + + Set to True when an abnormal noise is detected. + + + Sensor Value + R + Single + Mandatory + Float + + + Last or current measured value from the Sensor. + + + Minimum measured value + R + Single + Optional + Float + + dB + Minimum value measured by the sensor since power ON or reset. + + + Maximum measured value + R + Single + Optional + Float + + dB + Maximum value measured by the sensor since power ON or reset. + + + Minimum range value + R + Single + Optional + Float + + dB + Minimum value that can be measured by the sensor. + + + Maximum range value + R + Single + Optional + Float + + dB + Maximum value that can be measured by the sensor. + + + Reset min and max values + E + Single + Optional + + + + Reset the min and max measured values to current value. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3434.xml b/application/src/main/data/lwm2m-registry/3434.xml new file mode 100644 index 0000000000..b06852cbe1 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3434.xml @@ -0,0 +1,128 @@ + + + + + + + People counter + The uCIFI people counter provides people counting information using Bluetooth beacon or any other method. + 3434 + urn:oma:lwm2m:ext:3434 + 1.0 + 1.0 + Multiple + Optional + + + Actual number of persons + R + Single + Mandatory + Integer + + + Number of persons currently identified by the device. + + + Daily number of persons + R + Single + Optional + Integer + + + Cumulated number of persons detected by the device since beginning of the day. + + + Cumulated number of persons + R + Single + Optional + Integer + + + Cumulated number of persons detected by the device since last reset. + + + Reset of cumulated number + E + Single + Optional + + + + Reset the cumulated number of persons. + + + Daily number of passages + R + Single + Optional + Integer + + + Number of passages (same people could be counted multiple times if identified several times) today. + + + Cumulated number of passages + R + Single + Optional + Integer + + + Cumulated number of passages since last reset. + + + Reset of cumulated number of passages + E + Single + Optional + + + + Reset the cumulated number of passages. + + + Type of sensor + RW + Single + Optional + String + + + Type of sensor (e.g. Bluetooth beacon, WIFI detector). + + + + + diff --git a/application/src/main/data/lwm2m-registry/3435.xml b/application/src/main/data/lwm2m-registry/3435.xml new file mode 100644 index 0000000000..1694afc32b --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3435.xml @@ -0,0 +1,178 @@ + + + + + + + Filling level + The uCIFI filling level sensor measures how full and/or how empty a container (e.g. waste, fuel) is and reports it either in percentage or in centimeters. The filling level sensor may be complemented with a temperature sensor to compose a waste filling sensor that can also identify waste container fire. + 3435 + urn:oma:lwm2m:ext:3435 + 1.0 + 1.0 + Multiple + Optional + + + Container height + RW + Single + Optional + Integer + + cm + Height of the container. + + + Actual filling percentage + R + Single + Mandatory + Float + + /100 + Percentage of container filled with content. + + + Actual filling level + R + Single + Optional + Integer + + cm + Height of content in the container. + + + High threshold + RW + Single + Optional + Float + + /100 + Threshold above which the container is considered full. + + + Container full + R + Single + Mandatory + Boolean + + + Set to True if the actual filling percentage is above the high threshold. + + + Low threshold + RW + Single + Optional + Float + + /100 + Threshold below which the container is considered empty. + + + Container empty + R + Single + Mandatory + Boolean + + + Set to True if the actual filling percentage is below the low threshold. + + + Average filling speed + R + Single + Optional + Float + + /100 + Average percentage filled per day. + + + Average filling speed reset + E + Single + Optional + + + + Reset average filling speed. + + + Forecast full date + R + Single + Optional + Time + + + Next date at which the container should reach the high threshold. + + + Forecast empty date + R + Single + Optional + Time + + + Next date at which the container should reach the low threshold. + + + Container out of location + R + Single + Optional + Boolean + + + Set to True if the container is not at the location where it should be. + + + Container out of position + R + Single + Optional + Boolean + + + Set to True if the container is not in correct upright position. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3436.xml b/application/src/main/data/lwm2m-registry/3436.xml new file mode 100644 index 0000000000..a450edf280 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3436.xml @@ -0,0 +1,98 @@ + + + + + + + Edge computing manager + The uCIFI edge computing manager object defines the capability of a device to run scripts on the device itself. + 3436 + urn:oma:lwm2m:ext:3436 + 1.0 + 1.0 + Single + Optional + + + Max number of scripts + R + Single + Optional + Integer + + + Maximum number of scripts supported by the device. + + + Number of free slots for scripts + R + Single + Optional + Integer + + + Number of free slots to store new scripts. + + + Max number of conditions + R + Single + Optional + Integer + + + Maximum number of conditions supported by the device. + + + Number of free slots for conditions + R + Single + Optional + Integer + + + Number of free slots to store new conditions. + + + Edge-computing capabilities + R + Single + Optional + String + + + Vendor-specific resource to share the capabilities of the devices as regards to edge computing. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3437.xml b/application/src/main/data/lwm2m-registry/3437.xml new file mode 100644 index 0000000000..284911326e --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3437.xml @@ -0,0 +1,118 @@ + + + + + + + Edge computing script + The uCIFI edge computing scripts object defines a particular script to be executed by a device under a time or any other condition set by a "Edge Computing Time Condition" or "Edge Computing Generic Condition" object. + 3437 + urn:oma:lwm2m:ext:3437 + 1.0 + 1.0 + Multiple + Optional + + + Name + RW + Single + Optional + String + + + Name of the script. + + + Signature + RW + Single + Optional + String + + + Unique hash or signature associated to the script to be verified by the end-device before execution. The format of the hash or signature is vendor-specific. + + + Version + RW + Single + Optional + String + + + Version of the script, as defined by the vendor. + + + Script + RW + Single + Mandatory + Opaque + + + Vendor-specific script. + + + Script format + RW + Single + Optional + String + + + Script format to enable the device to select the script interpreter in case it supports multiple (e.g. LUA). The format of this resource is free to support vendor-specific formats. + + + Non supported script + R + Single + Optional + Boolean + + + Set to True if the script is not supported or if the device has identified errors in the script. + + + Non authorized script + R + Single + Optional + Boolean + + + Set to True if the hash/signature is not correct. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3438.xml b/application/src/main/data/lwm2m-registry/3438.xml new file mode 100644 index 0000000000..2e2405c161 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3438.xml @@ -0,0 +1,78 @@ + + + + + + + Edge computing time condition + The uCIFI edge computing time condition object defines the day/time (defined as a CRON rule) at which a particular script should be executed by a device. + 3438 + urn:oma:lwm2m:ext:3438 + 1.0 + 1.0 + Multiple + Optional + + + Execution time/day + RW + Single + Mandatory + String + + + Date/time at which the script should be executed, formatted as a CRON rule. + + + Script identifier + RW + Multiple + Mandatory + String + + + Object URN/Resource ID of the script to apply to the times/days defined in the execution time/day resource. + + + Non supported time condition + R + Single + Optional + Boolean + + + Set to True if the device can’t support the condition. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3439.xml b/application/src/main/data/lwm2m-registry/3439.xml new file mode 100644 index 0000000000..b702866ce0 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3439.xml @@ -0,0 +1,78 @@ + + + + + + + Edge computing generic condition + The uCIFI edge computing generic condition object defines a vendor-specific condition (e.g. when a resource or an object is equal to a particular value) at which a particular script should be executed by the device. + 3439 + urn:oma:lwm2m:ext:3439 + 1.0 + 1.0 + Multiple + Optional + + + Vendor condition + RW + Single + Optional + Opaque + + + Condition described in a vendor specific way. + + + Script identifier + RW + Multiple + Mandatory + String + + + Object URN/Resource ID of the script to apply to the time/days defined in the vendor condition resource. + + + Non supported time condition + R + Single + Optional + Boolean + + + Set to True if the device can’t support the condition. + + + + + diff --git a/application/src/main/data/lwm2m-registry/3441.xml b/application/src/main/data/lwm2m-registry/3441.xml new file mode 100644 index 0000000000..bdd5efca6c --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3441.xml @@ -0,0 +1,262 @@ + + + + + LwM2M v1.0 Test Object + + 3441 + urn:oma:lwm2m:ext:3441 + 1.0 + 1.0 + Multiple + Optional + + + Reset values + E + Single + Optional + + + + Reset all resources of this object with their initial value. + + + Randomize values + E + Single + Optional + + + + + + + + + Clear values + E + Single + Optional + + + + + + + + + Exec With Arguments + E + Single + Optional + + + + + + + + + Arguments List + R + Multiple + Optional + String + + + List of Arguments from last execute on "Exec With Arguments"(3) resource. This resource is not affected by the "Randomize values"(1) executable resource. + + + + String Value + RW + Single + Optional + String + + + Initial value must be "initial value". + + + + Integer Value + RW + Single + Optional + Integer + + + Initial value must be "1024". + + + + Float Value + RW + Single + Optional + Float + + + Initial value must be "3.14159". + + + + Boolean Value + RW + Single + Optional + Boolean + + + Initial value must be "true". + + + Opaque Value + RW + Single + Optional + Opaque + + + Initial value must be the bytes sequence "0123456789ABCDEF" (Hexadecimal notation). + + + + Time Value + RW + Single + Optional + Time + + + Initial value must be the time to an 1st, 2000 in the UTC time zone. (Timestamp value : 946684800) + + + ObjLink Value + RW + Single + Optional + Objlnk + + + Initial value must be a link to instance 0 of Device Object 3 (3:0). + + + Multiple String Value + RW + Multiple + Optional + String + + + Initial value must be 1 instance with ID 0 and value "initial value". + + + Multiple Integer Value + RW + Multiple + Optional + Integer + + + Initial value must be 1 instance with ID 0 and value "1024". + + + Multiple Float Value + RW + Multiple + Optional + Float + + + Initial value must be 1 instance with ID 0 and value "3.14159". + + + Multiple Boolean Value + RW + Multiple + Optional + Boolean + + + Initial value must be 1 instance with ID 0 and value "true". + + + Multiple Opaque Value + RW + Multiple + Optional + Opaque + + + Initial value must be 1 instance with ID 0 and value "0123456789ABCDEF"(Hexadecimal notation of the bytes sequence). + + + Multiple Time Value + RW + Multiple + Optional + Time + + + Initial value must be 1 instance with ID 0 and value 1st, 2000 in the UTC time zone (Timestamp value : 946684800). + + + Multiple ObjLink Value + RW + Multiple + Optional + Objlnk + + + Initial value must be 1 instance with ID 0 and value "3:0". + + + + + diff --git a/application/src/main/data/lwm2m-registry/3442.xml b/application/src/main/data/lwm2m-registry/3442.xml new file mode 100644 index 0000000000..b4607cead7 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/3442.xml @@ -0,0 +1,332 @@ + + + + + LwM2M v1.1 Test Object + + 3442 + urn:oma:lwm2m:ext:3442 + 1.1 + 1.0 + Multiple + Optional + + + Reset values + E + Single + Optional + + + + Reset all resources of this object with their initial value. + + + Randomize values + E + Single + Optional + + + + + + + + + Clear values + E + Single + Optional + + + + + + + + + Exec With Arguments + E + Single + Optional + + + + + + + + + Arguments List + R + Multiple + Optional + String + + + List of Arguments from last execute on "Exec With Arguments"(3) resource. This resource is not affected by "Randomize values"(1) executable resource. + + + + Send Data + E + Single + Optional + + + + + + + + + Resources to Send + RW + Multiple + Optional + String + + + List of path to LWM2M node (Object / Object Instance / Resource / Resource Instance) used by "Send Data "(5) resource. This resource is not affected by "Randomize values"(1) executable resource. + + + + String Value + RW + Single + Optional + String + + + Initial value must be "initial value". + + + + Integer Value + RW + Single + Optional + Integer + + + Initial value must be "1024". + + + + Unsigned Integer Value + RW + Single + Optional + Unsigned Integer + + + Initial value must be 2^63 (9223372036854775808). + + + + Float Value + RW + Single + Optional + Float + + + Initial value must be "3.14159". + + + + Boolean Value + RW + Single + Optional + Boolean + + + Initial value must be "true". + + + Opaque Value + RW + Single + Optional + Opaque + + + Initial value must be the bytes sequence "0123456789ABCDEF" (Hexadecimal notation). + + + + Time Value + RW + Single + Optional + Time + + + Initial value must be the time to an 1st, 2000 in the UTC time zone. (Timestamp value : 946684800) + + + ObjLink Value + RW + Single + Optional + Objlnk + + + Initial value must be a link to instance 0 of Device Object 3 (3:0). + + + CoreLnk Value + RW + Single + Optional + Corelnk + + + ".]]> + + + Multiple String Value + RW + Multiple + Optional + String + + + Initial value must be 1 instance with ID 0 and value "initial value". + + + Multiple Integer Value + RW + Multiple + Optional + Integer + + + Initial value must be 1 instance with ID 0 and value "1024". + + + Multiple Unsigned Integer Value + RW + Multiple + Optional + Unsigned Integer + + + Initial value must be 1 instance with ID 0 and value 2^63 (9223372036854775808). + + + + Multiple Float Value + RW + Multiple + Optional + Float + + + Initial value must be 1 instance with ID 0 and value "3.14159". + + + Multiple Boolean Value + RW + Multiple + Optional + Boolean + + + Initial value must be 1 instance with ID 0 and value "true". + + + Multiple Opaque Value + RW + Multiple + Optional + Opaque + + + Initial value must be 1 instance with ID 0 and value "0123456789ABCDEF"(Hexadecimal notation of the bytes sequence). + + + Multiple Time Value + RW + Multiple + Optional + Time + + + Initial value must be 1 instance with ID 0 and value 1st, 2000 in the UTC time zone (Timestamp value : 946684800). + + + Multiple ObjLink Value + RW + Multiple + Optional + Objlnk + + + Initial value must be 1 instance with ID 0 and value "3:0". + + + Multiple CoreLnk Value + RW + Multiple + Optional + Corelnk + + + ".]]> + + + + + diff --git a/application/src/main/data/lwm2m-registry/4.xml b/application/src/main/data/lwm2m-registry/4.xml new file mode 100644 index 0000000000..dc3f100cc4 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/4.xml @@ -0,0 +1,258 @@ + + + + + + + Connectivity Monitoring + + 4 + urn:oma:lwm2m:oma:4:1.3 + 1.1 + 1.3 + Single + Optional + + + Network Bearer + R + Single + Mandatory + Integer + 0..50 + + + + + Available Network Bearer + R + Multiple + Mandatory + Integer + 0..50 + + + + + Radio Signal Strength + R + Single + Mandatory + Integer + + dBm + + + + Link Quality + R + Single + Optional + Integer + + + + + + IP Addresses + R + Multiple + Mandatory + String + + + + + + Router IP Addresses + R + Multiple + Optional + String + + + + + + Link Utilization + R + Single + Optional + Integer + 0..100 + /100 + + + + APN + R + Multiple + Optional + String + + + + + + Cell ID + R + Single + Optional + Integer + + + + + + SMNC + R + Single + Optional + Integer + 0..999 + + + + + SMCC + R + Single + Optional + Integer + 0..999 + + + + SignalSNR + R + Single + Optional + Integer + + dB + + + + LAC + R + Single + Optional + Integer + + + + + + Coverage Enhancement Level + R + Single + Optional + Integer + 0..4 + + + + + + diff --git a/application/src/main/data/lwm2m-registry/5.xml b/application/src/main/data/lwm2m-registry/5.xml new file mode 100644 index 0000000000..02188533b2 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/5.xml @@ -0,0 +1,255 @@ + + + + + + + Firmware Update + + 5 + urn:oma:lwm2m:oma:5:1.1 + 1.1 + 1.1 + Single + Optional + + + Package + W + Single + Mandatory + Opaque + + + + + + Package URI + RW + Single + Mandatory + String + 0..255 + + + + + Update + E + Single + Mandatory + + + + + + + State + R + Single + Mandatory + Integer + 0..3 + + + + + Update Result + R + Single + Mandatory + Integer + 0..11 + + + + + PkgName + R + Single + Optional + String + 0..255 + + + + + PkgVersion + R + Single + Optional + String + 0..255 + + + + + Firmware Update Protocol Support + R + Multiple + Optional + Integer + 0..5 + + + + + Firmware Update Delivery Method + R + Single + Mandatory + Integer + 0..2 + + + + + Cancel + E + Single + Optional + + + + + + + Severity + RW + Single + Optional + Integer + 0..2 + + + + + Last State Change Time + R + Single + Optional + Time + + + + + + Maximum Defer Period + RW + Single + Optional + Unsigned Integer + + s + + + + + + diff --git a/application/src/main/data/lwm2m-registry/500.xml b/application/src/main/data/lwm2m-registry/500.xml new file mode 100644 index 0000000000..df21a5aa80 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/500.xml @@ -0,0 +1,189 @@ + + + + + + + CoAP Config + + + + 500 + urn:oma:lwm2m:oma:500 + 1.0 + 1.0 + Multiple + Optional + + + Network Bearer + RW + Single + Mandatory + Integer + 0..255 + + + + + + + ACK_TIMEOUT + RW + Single + Mandatory + Integer + + s + + + + + + ACK_RANDOM_FACTOR + RW + Single + Mandatory + Float + + + + + + + + MAX_RETRANSMIT + RW + Single + Mandatory + Integer + + + + + + + + NSTART + RW + Single + Mandatory + Integer + + + + + + + + DEFAULT_LEISURE + RW + Single + Optional + Integer + + s + + + + + + PROBING_RATE + RW + Single + Optional + Integer + + B/s + + + + + + DTLS Retransmission Timer + RW + Single + Optional + Integer + + s + + + + + + Max Response Waiting Time + RW + Single + Optional + Integer + + + + + + + + Apply + E + Single + Optional + + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/501.xml b/application/src/main/data/lwm2m-registry/501.xml new file mode 100644 index 0000000000..402cfc0b78 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/501.xml @@ -0,0 +1,163 @@ + + + + + + + LTE-MTC Band Config + + + + 501 + urn:oma:lwm2m:oma:501 + 1.0 + 1.0 + Multiple + Optional + + + EARFCN + RW + Single + Mandatory + Integer + 0..65535 + + + + + + + Current Frequency (Downlink) + R + Single + Mandatory + Float + + MHz + + + + + + Current Frequency (Uplink) + R + Single + Optional + Float + + MHz + + + + + + Prioritized Channels + RW + Multiple + Mandatory + Integer + + + + + + + + Application Type + RW + Single + Optional + String + + + + + + + + Reset Timeout + RW + Single + Mandatory + Float + + s + + + + + + Reset + E + Single + Mandatory + + + + + + + + + Vendor specific extensions + R + Single + Optional + Objlnk + + + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/502.xml b/application/src/main/data/lwm2m-registry/502.xml new file mode 100644 index 0000000000..5b85aa4515 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/502.xml @@ -0,0 +1,214 @@ + + + + + + + CO Detector + + 502 + urn:oma:lwm2m:oma:502 + 1.0 + 1.0 + Multiple + Optional + + + CO Sensor Location Tag + RW + Single + Optional + String + + + + + + CO Detected + R + Single + Mandatory + Boolean + + + + + + Ambient CO Value + RW + Single + Optional + Float + 0..12800 + ppm + + + + CO Value + R + Single + Mandatory + Float + 0..12800 + ppm + + + + Min CO Range Value + R + Single + Optional + Float + 0..12800 + ppm + + + + Max CO Range Value + R + Single + Optional + Float + 0..12800 + ppm + + + + CO Detection Accuracy + R + Single + Optional + Float + 0..12800 + ppm + + + + Minimum Measured CO Value + R + Single + Optional + Float + 0..12800 + ppm + + + + Maximum Measured CO Value + R + Single + Optional + Float + 0..12800 + ppm + + + + Upper CO Threshold + RW + Single + Optional + Float + 0..12800 + ppm + + + + Reset + E + Single + Optional + + + + + + + Alarm loudness + R + Single + Optional + Float + + dB + + + + Latitude + R + Single + Optional + String + + + + + + Longitude + R + Single + Optional + String + + + + + + Altitude + R + Single + Optional + Float + + m + + + + Battery Percentage + R + Single + Optional + Float + 0.00..100.00 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/503.xml b/application/src/main/data/lwm2m-registry/503.xml new file mode 100644 index 0000000000..6c6e627a94 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/503.xml @@ -0,0 +1,434 @@ + + + + + + + Fire Alarm + + 503 + urn:oma:lwm2m:oma:503 + 1.0 + 1.0 + Multiple + Optional + + + Temperature Sensor Location Tag + RW + Single + Optional + String + + + + + + Temperature Unit + RW + Single + Mandatory + Integer + + + + + + Ambient Temperature + RW + Single + Optional + Float + + + + + + Temperature Value + R + Single + Mandatory + Float + + + + + + Minimum Measured Temperature Value + R + Single + Optional + Float + + + + + + Maximum Measured Temperature Value + R + Single + Optional + Float + + + + + + Min Temperature Range Value + RW + Single + Optional + Float + + + + + + Max Temperature Range Value + RW + Single + Optional + Float + + + + + + Lower Temperature Accuracy + RW + Single + Optional + Float + + + + + + Upper Temperature Accuracy + RW + Single + Optional + Float + + + + + + Temperature Threshold + RW + Single + Optional + Float + + + + + + Temperature Sensor Error + R + Single + Optional + Boolean + + + + + + Reset Temperature + E + Single + Optional + + + + + + + Temperature Alarm Loudness + RW + Single + Optional + Float + + dB + + + + Humidity Sensor Location Tag + RW + Single + Optional + String + + + + + + Ambient Relative Humidity + RW + Single + Optional + Float + 0.0..100.0 + %RH + + + + Humidity Value + R + Single + Mandatory + Float + 0.0..100.0 + /100 + + + + Minimum Measured Humidity Value + R + Single + Optional + Float + 0.0..100.0 + /100 + + + + Maximum Measured Humidity Value + R + Single + Optional + Float + 0.0..100.0 + /100 + + + + Humidity Accuracy + RW + Single + Optional + Float + 0.0..100.0 + /100 + + + + Humidity Threshold + RW + Single + Optional + Float + 0.0..100.0 + /100 + + + + Humidity Sensor Error + R + Single + Optional + Boolean + + + + + + Reset Humidity + E + Single + Optional + + + + + + + Humidity Alarm Loudness + RW + Single + Optional + Float + + dB + + + + Smoke Sensor Location Tag + RW + Single + Optional + String + + + + + + CO2 Alarm State + R + Single + Mandatory + Boolean + + + + + + Ambient CO2 Value + R + Single + Optional + Float + + ppm + + + + CO2 Value + R + Single + Mandatory + Float + + ppm + + + + Minimum Measured CO2 Value + R + Single + Optional + Float + + ppm + + + + Maximum Measured CO2 Value + R + Single + Optional + Float + + ppm + + + + CO2 Threshold + RW + Single + Optional + Float + + ppm + + + + Smoke Sensor Error + R + Single + Optional + Boolean + + + + + + Reset Smoke Detection + E + Single + Optional + + + + + + + Smoke Alarm Loudness + R + Single + Optional + Float + + dB + + + + Battery Percentage + R + Single + Optional + Float + 0.00..100.00 + + + + + Latitude + R + Single + Optional + String + + + + + + Longitude + R + Single + Optional + String + + + + + + Altitude + R + Single + Optional + Float + + m + + + + + + diff --git a/application/src/main/data/lwm2m-registry/504.xml b/application/src/main/data/lwm2m-registry/504.xml new file mode 100644 index 0000000000..25c033f59d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/504.xml @@ -0,0 +1,341 @@ + + + + + + + Remote SIM Provisioning + + 504 + urn:oma:lwm2m:oma:504 + 1.0 + 1.0 + Multiple + Optional + + + Current SIM Type + R + Single + Mandatory + Integer + 0..3 + + + + + Supported SIM Type + R + Multiple + Mandatory + Integer + 0..3 + + + + + Service Provider Name + R + Single + Optional + String + + + + + + Download URI/Information + RW + Single + Optional + String + 0..255 + + + + + RSP Update + E + Single + Optional + + + + + + + RSP Update State + R + Single + Optional + Integer + 0..10 + + + + + RSP Update Result + R + Single + Optional + Integer + 0..16 + + + + + Profile Name + R + Single + Optional + String + 0..255 + + + + + Profile Package Version + R + Single + Optional + String + 0..255 + + + + + Free Memory on SIM + R + Single + Optional + Integer + + KiB + + + + Total Memory on SIM + R + Single + Optional + Integer + + KiB + + + + Integrated Circuit Card Identifier (ICCID) + R + Multiple + Mandatory + String + + + + + + eUICC ID + R + Single + Optional + String + + + + + + RSP Type + R + Single + Mandatory + Integer + + + + + + Selected ICCID for RSP operation + RW + Single + Optional + String + + + + + + RSP Consent Reason + R + Single + Optional + String + + + + + + RSP User Consent + W + Single + Optional + Integer + 0..1 + + + + + RSP Confirmation Code + W + Single + Optional + String + + + + + + RSP Data Type + RW + Single + Optional + Integer + 0..17 + + + + + Set RSP Data + W + Single + Optional + Opaque + + + + + + Get RSP Data + R + Single + Optional + Opaque + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/505.xml b/application/src/main/data/lwm2m-registry/505.xml new file mode 100644 index 0000000000..a89c59ca41 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/505.xml @@ -0,0 +1,262 @@ + + + + + + + LwM2M SIM Provisioning + + 505 + urn:oma:lwm2m:oma:505 + 1.0 + 1.0 + Multiple + Optional + + + Current SIM Type + R + Single + Mandatory + Integer + 0..3 + + + + + Supported SIM Type + R + Multiple + Mandatory + Integer + 0..3 + + + + + Service Provider Name + R + Single + Mandatory + String + + + + + + Profile Package + W + Single + Optional + Opaque + + + + + + Profile URI + RW + Single + Optional + String + 0..255 + + + + + Update Profile + E + Single + Optional + + + + + + + Update State + R + Single + Mandatory + Integer + 0..3 + + + + + Update Result + R + Single + Mandatory + Integer + 0..8 + + + + + Profile Name + R + Single + Optional + String + 0..255 + + + + + Profile Package Version + R + Single + Optional + String + 0..255 + + + + + Profile Update Protocol Support + R + Multiple + Optional + Integer + 0..3 + + + + + Profile Update Delivery Method + R + Single + Mandatory + Integer + 0..2 + + + + + Free Memory on SIM + R + Single + Optional + Integer + + KiB + + + + Total Memory on SIM + R + Single + Optional + Integer + + KiB + + + + Integrated Circuit Card Identifier (ICCID) + R + Multiple + Optional + Integer + + + + + + eUICC ID + R + Single + Optional + String + + + + + + Profile Type + R + Single + Optional + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/507.xml b/application/src/main/data/lwm2m-registry/507.xml new file mode 100644 index 0000000000..650a0e8904 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/507.xml @@ -0,0 +1,115 @@ + + + + + + nuSIM Profile + This device management object contains the elements of a nuSIM profile package according to nuSIM Technical Specification NTS.01. + 507 + urn:oma:lwm2m:oma:507 + 1.0 + 1.0 + Multiple + Optional + + + encP + RW + Single + Mandatory + String + + + + + mac + RW + Single + Mandatory + String + + + + + eKpubDP + RW + Single + Mandatory + String + + + + + + SigEKpubDP + RW + Single + Mandatory + String + + + + + + KpubDP + RW + Single + Mandatory + String + + + + + + SigKpubDP + RW + Single + Mandatory + String + + + + + + KpubCl + RW + Single + Mandatory + String + + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/508.xml b/application/src/main/data/lwm2m-registry/508.xml new file mode 100644 index 0000000000..7f45059dd5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/508.xml @@ -0,0 +1,115 @@ + + + + + + nuSIM + This device management object represents a nuSIM integrated SIM according to NTS.01 - nuSIM Integrated SIM Specification. + 508 + urn:oma:lwm2m:oma:508 + 1.0 + 1.0 + Single + Optional + + + EID + R + Single + Mandatory + String + + + + + + ICCID + R + Single + Mandatory + String + + + + + + nuSIM Capabilities + R + Single + Mandatory + Opaque + + + + + nuSIM Cert + R + Single + Optional + String + + + + + + nuSIM Profile + RW + Single + Mandatory + Objlnk + + + + + Load Profile + E + Single + Mandatory + + + + + + + Load Profile Result + R + Single + Mandatory + Integer + 0..255 + + + + + + + diff --git a/application/src/main/data/lwm2m-registry/509.xml b/application/src/main/data/lwm2m-registry/509.xml new file mode 100644 index 0000000000..17ff50250f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/509.xml @@ -0,0 +1,138 @@ + + + + + + + Measurement Metadata + This object contains measurement metadata for sensor objects. The corresponding sensor is identified with an object link. If the LwM2M Client provides measurement quality information both inside the sensor objects and in the Measurement Metadata object, it MUST ensure that identical data is reported in corresponding object instances. The LwM2M Server SHOULD use the data from the Measurement Metadata object if both are available. The LwM2M Client also MUST ensure that no two Measurement Metadata object instances link to the same sensor object instance. If the LwM2M Server receives such non-conforming data, the Measurement Metadata object instance with largest Instance ID SHOULD be used. + + 509 + urn:oma:lwm2m:oma:509 + 1.1 + 1.0 + Multiple + Optional + + + Linked Sensor + R + Single + Mandatory + Objlnk + + + Link to the sensor object instance that this metadata relates to. + + + + Measurement Quality Reason Code + R + Multiple + Optional + Integer + 0..1023 + + Measurement quality degradation reason reported by a smart sensor. A degraded measurement has Measurement Quality Indicator value 1-3 or Measurement Quality Level less than 100. +0: UNKNOWN Reason is unknown. +1: UNDER_RANGE Measured value is under measurement range. +2: OVER_RANGE Measured value is over measurement range. +3: NOISY Measured value seems to change too quickly. +4: STUCK_VALUE Measured value seems to change too slowly. +5: STEP_TOO_HIGH Measured value has changed too much. +6: CALIBRATION_EXPIRED Sensor calibration has expired. +7: WARMING_UP Sensor is warming up. +8: SERVICE_MODE Sensor is under service. +9: HUMAN_INTERFERENCE Human presence near the sensor. +10: NATURAL_INTERFERENCE Natural phenomenon or animal presence. +11: CONTAMINATION Contamination of the sensor. +12: HW_FAILURE Sensor hardware has failed. +13: LACK_OF_DATA_SAMPLES Data sampling is lossy. +14: OUT_OF_OPERATING_RANGE Environment conditions exceeded. +15: AUTO_CALIBRATION Auto calibration in progress. +16-127: RESERVED_GENERIC_BLOCK Reserved generic block. +128-511: VENDOR_SPECIFIC_BLOCK Vendor specific block. +512-1023: RESERVED_VENDOR_BLOCK Reserved vendor specific block. + + + + Measurement Quality Indicator + R + Single + Optional + Integer + 0..23 + + Measurement quality indicator reported by a smart sensor. +0: UNCHECKED No quality checks were done because they do not exist or can not be applied. +1: REJECTED WITH CERTAINTY The measured value is invalid. +2: REJECTED WITH PROBABILITY The measured value is likely invalid. +3: ACCEPTED BUT SUSPICIOUS The measured value is likely OK. +4: ACCEPTED The measured value is OK. +5-15: Reserved for future extensions. +16-23: Vendor specific measurement quality. + + + + Measurement Quality Level + R + Single + Optional + Integer + 0..100 + + Measurement quality level reported by a smart sensor. Quality level 100 means that the measurement has fully passed quality check algorithms. Smaller quality levels mean that quality has decreased and the measurement has only partially passed quality check algorithms. The smaller the quality level, the more caution should be used by the application when using the measurement. When the quality level is 0 it means that the measurement should certainly be rejected. + + + + + + diff --git a/application/src/main/data/lwm2m-registry/510.xml b/application/src/main/data/lwm2m-registry/510.xml new file mode 100644 index 0000000000..302377bbff --- /dev/null +++ b/application/src/main/data/lwm2m-registry/510.xml @@ -0,0 +1,103 @@ + + + + + + + Vendor Specific Measurement Quality Reason + This object contains description for vendor specific measurement quality reason codes provided in a Measurement Metadata (509) object. Instances of this object define the mapping of vendor specific reason codes to reason description in the scope of an individual device. + + 510 + urn:oma:lwm2m:oma:510 + 1.1 + 1.0 + Multiple + Optional + + + Vendor Specific Quality Reason Code + R + Single + Mandatory + Integer + 128..1023 + + Measurement quality degradation reason reported by a smart sensor. Reason codes 0-127 are generic reason codes and they are defined in Measurement Metadata object description. +128-511: VENDOR_SPECIFIC_BLOCK Vendor specific block. This block contains vendor specific quality reason codes. +512-1023: RESERVED_VENDOR_BLOCK Reserved vendor specific block. This block is reserved for future use. + + + + Quality Reason Name + R + Single + Mandatory + String + 1..128 + + The name of the quality reason. It is used as a key to find a localized reason description. The name should be concise and unique in the localization scope. + + + + Quality Reason Description + R + Single + Mandatory + String + + + Description of the quality reason. This text can be used if a localized version is not found. + + + + + + diff --git a/application/src/main/data/lwm2m-registry/6.xml b/application/src/main/data/lwm2m-registry/6.xml new file mode 100644 index 0000000000..8b1b3da625 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/6.xml @@ -0,0 +1,143 @@ + + + + + + + Location + + 6 + urn:oma:lwm2m:oma:6 + 1.0 + 1.0 + Single + Optional + + + Latitude + R + Single + Mandatory + Float + + lat + + + + Longitude + R + Single + Mandatory + Float + + lon + + + + Altitude + R + Single + Optional + Float + + m + + + + Radius + R + Single + Optional + Float + + m + + + + Velocity + R + Single + Optional + Opaque + + + + + + Timestamp + R + Single + Mandatory + Time + + + + + Speed + R + Single + Optional + Float + + m/s + + + + + diff --git a/application/src/main/data/lwm2m-registry/7.xml b/application/src/main/data/lwm2m-registry/7.xml new file mode 100644 index 0000000000..451780da1f --- /dev/null +++ b/application/src/main/data/lwm2m-registry/7.xml @@ -0,0 +1,164 @@ + + + + + + + Connectivity Statistics + + 7 + urn:oma:lwm2m:oma:7 + 1.0 + 1.0 + Single + Optional + + + SMS Tx Counter + R + Single + Optional + Integer + + + + + + SMS Rx Counter + R + Single + Optional + Integer + + + + + + Tx Data + R + Single + Optional + Integer + + + + + + Rx Data + R + Single + Optional + Integer + + + + + + Max Message Size + R + Single + Optional + Integer + + B + + + + Average Message Size + R + Single + Optional + Integer + + B + + + + Start + E + Single + Mandatory + + + + + + Stop + E + Single + Mandatory + + + + + + Collection Period + RW + Single + Optional + Integer + + s + + + + + + diff --git a/application/src/main/data/lwm2m-registry/8.xml b/application/src/main/data/lwm2m-registry/8.xml new file mode 100644 index 0000000000..a9296999f5 --- /dev/null +++ b/application/src/main/data/lwm2m-registry/8.xml @@ -0,0 +1,141 @@ + + + + + + + Lock and Wipe + + 8 + urn:oma:lwm2m:oma:8 + 1.0 + 1.0 + Single + Optional + + State + RW + Single + Mandatory + Integer + 0..2 + + + + Lock target + W + Multiple + Mandatory + String + + + + + Wipe item + R + Multiple + Optional + String + + + + + Wipe + E + Single + Mandatory + + + + + + Wipe target + W + Multiple + Mandatory + String + + + + Lock or Wipe Operation Result + R + Single + Mandatory + Integer + 0..8 + + + + + + diff --git a/application/src/main/data/lwm2m-registry/9.xml b/application/src/main/data/lwm2m-registry/9.xml new file mode 100644 index 0000000000..4186b1248d --- /dev/null +++ b/application/src/main/data/lwm2m-registry/9.xml @@ -0,0 +1,321 @@ + + + + + + + LWM2M Software Management + + 9 + urn:oma:lwm2m:oma:9 + 1.0 + 1.0 + Multiple + Optional + + + PkgName + R + Single + Mandatory + String + 0..255 bytes + + + + + PkgVersion + R + Single + Mandatory + String + 0..255 bytes + + + + + Package + W + Single + Optional + Opaque + + + + + + Package URI + W + Single + Optional + String + 0..255 bytes + + + + + + Install + E + Single + Mandatory + + + + + + + Checkpoint + R + Single + Optional + Objlnk + + + + + + Uninstall + E + Single + Mandatory + + + + + + + Update State + R + Single + Mandatory + Integer + 0..4 + + + + + Update Supported Objects + RW + Single + Optional + Boolean + + + + + + Update Result + R + Single + Mandatory + Integer + 0..200 + + + + + Activate + E + Single + Mandatory + + + + + + + Deactivate + E + Single + Mandatory + + + + + + + Activation State + R + Single + Mandatory + Boolean + + + + + + Package Settings + RW + Single + Optional + Objlnk + + + + + + User Name + W + Single + Optional + String + 0..255 bytes + + + + + Password + W + Single + Optional + String + 0..255 bytes + + + + + Status Reason + R + Single + Optional + String + + + + + + Software Component Link + R + Multiple + Optional + Objlnk + + + + + + Software Component tree length + R + Single + Optional + Integer + 0..255 + + + + + + + diff --git a/application/src/main/data/upgrade/1.3.0/schema_update.cql b/application/src/main/data/upgrade/1.3.0/schema_update.cql index 88cb1dce81..da20e2b6d0 100644 --- a/application/src/main/data/upgrade/1.3.0/schema_update.cql +++ b/application/src/main/data/upgrade/1.3.0/schema_update.cql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/1.3.1/schema_update.sql b/application/src/main/data/upgrade/1.3.1/schema_update.sql index 1ca6590835..a0f1ef83b7 100644 --- a/application/src/main/data/upgrade/1.3.1/schema_update.sql +++ b/application/src/main/data/upgrade/1.3.1/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.cql b/application/src/main/data/upgrade/1.4.0/schema_update.cql index 138c9f9086..b41f5b82d5 100644 --- a/application/src/main/data/upgrade/1.4.0/schema_update.cql +++ b/application/src/main/data/upgrade/1.4.0/schema_update.cql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.sql b/application/src/main/data/upgrade/1.4.0/schema_update.sql index c999c6b3e9..435a3d05d0 100644 --- a/application/src/main/data/upgrade/1.4.0/schema_update.sql +++ b/application/src/main/data/upgrade/1.4.0/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.0.0/schema_update.cql b/application/src/main/data/upgrade/2.0.0/schema_update.cql index 5f7daa0ea8..279b21c489 100644 --- a/application/src/main/data/upgrade/2.0.0/schema_update.cql +++ b/application/src/main/data/upgrade/2.0.0/schema_update.cql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.0.0/schema_update.sql b/application/src/main/data/upgrade/2.0.0/schema_update.sql index 4a07d571e8..d110c2d232 100644 --- a/application/src/main/data/upgrade/2.0.0/schema_update.sql +++ b/application/src/main/data/upgrade/2.0.0/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.cql b/application/src/main/data/upgrade/2.1.1/schema_update.cql index 49aa012d70..8fede09387 100644 --- a/application/src/main/data/upgrade/2.1.1/schema_update.cql +++ b/application/src/main/data/upgrade/2.1.1/schema_update.cql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.sql b/application/src/main/data/upgrade/2.1.1/schema_update.sql index a840af7e4f..4589d405dd 100644 --- a/application/src/main/data/upgrade/2.1.1/schema_update.sql +++ b/application/src/main/data/upgrade/2.1.1/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.1.2/schema_update.cql b/application/src/main/data/upgrade/2.1.2/schema_update.cql index 0a27a912a0..a0b5ca5b90 100644 --- a/application/src/main/data/upgrade/2.1.2/schema_update.cql +++ b/application/src/main/data/upgrade/2.1.2/schema_update.cql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.1.2/schema_update.sql b/application/src/main/data/upgrade/2.1.2/schema_update.sql index d7baf2ec7e..5dcc63dc2f 100644 --- a/application/src/main/data/upgrade/2.1.2/schema_update.sql +++ b/application/src/main/data/upgrade/2.1.2/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.2.0/schema_update.sql b/application/src/main/data/upgrade/2.2.0/schema_update.sql index 9fc8d11189..568d0031fa 100644 --- a/application/src/main/data/upgrade/2.2.0/schema_update.sql +++ b/application/src/main/data/upgrade/2.2.0/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.3.1/schema_update.sql b/application/src/main/data/upgrade/2.3.1/schema_update.sql index 7fd717e253..33c3502ad7 100644 --- a/application/src/main/data/upgrade/2.3.1/schema_update.sql +++ b/application/src/main/data/upgrade/2.3.1/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.4.0/schema_update.sql b/application/src/main/data/upgrade/2.4.0/schema_update.sql index 835b1940ea..daf9f5f2b4 100644 --- a/application/src/main/data/upgrade/2.4.0/schema_update.sql +++ b/application/src/main/data/upgrade/2.4.0/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.4.2/schema_update.sql b/application/src/main/data/upgrade/2.4.2/schema_update.sql index d99aaa4d94..0923883a97 100644 --- a/application/src/main/data/upgrade/2.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/2.4.2/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql index 936911b6a1..9c05371d3a 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql index 5cf51d13ed..0c9e250035 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql index 58f69d034d..8d5bc061ad 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql b/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql index 2a5ca58092..46f6e0f675 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql b/application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql index a270af1d53..67d5980da2 100644 --- a/application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql +++ b/application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql b/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql index 01f443151d..ea382dc829 100644 --- a/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql +++ b/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index a23150cab9..3e666910c7 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_after.sql b/application/src/main/data/upgrade/3.1.1/schema_update_after.sql index 75cb865b69..97d767cdeb 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update_after.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update_after.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql index 0d74063bb1..fc55cb18f3 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.2.1/schema_update.sql b/application/src/main/data/upgrade/3.2.1/schema_update.sql index 04dc38a28d..ea25872753 100644 --- a/application/src/main/data/upgrade/3.2.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.1/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql b/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql index cecbf5aff1..407a2e1727 100644 --- a/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql +++ b/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index 8685bf3ccf..c901dc8783 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.2.2/schema_update_event.sql b/application/src/main/data/upgrade/3.2.2/schema_update_event.sql index 9e2e6cfd33..8f43d7e90e 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update_event.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update_event.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql b/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql index 42a7e8b277..a765ce1957 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.3.2/schema_update.sql b/application/src/main/data/upgrade/3.3.2/schema_update.sql index 5501c359cb..fde6a83dc2 100644 --- a/application/src/main/data/upgrade/3.3.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.3.2/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql index dec4f269f3..fe31a67d55 100644 --- a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql +++ b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql b/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql index 432fca8786..79862cdee7 100644 --- a/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql +++ b/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.3.3/schema_update.sql b/application/src/main/data/upgrade/3.3.3/schema_update.sql index eed1cab1ea..5d67f09baf 100644 --- a/application/src/main/data/upgrade/3.3.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.3.3/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.3.4/schema_update.sql b/application/src/main/data/upgrade/3.3.4/schema_update.sql index c86a4fe1f4..566ef25aeb 100644 --- a/application/src/main/data/upgrade/3.3.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.3.4/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.4.0/schema_update.sql b/application/src/main/data/upgrade/3.4.0/schema_update.sql index 71588d88ac..38f4e6d283 100644 --- a/application/src/main/data/upgrade/3.4.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.0/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.4.1/schema_update.sql b/application/src/main/data/upgrade/3.4.1/schema_update.sql index 2b7a4bdf26..fe012f8453 100644 --- a/application/src/main/data/upgrade/3.4.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.1/schema_update.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_after.sql b/application/src/main/data/upgrade/3.4.1/schema_update_after.sql index 78df4d2fd2..d1d8ae4dbe 100644 --- a/application/src/main/data/upgrade/3.4.1/schema_update_after.sql +++ b/application/src/main/data/upgrade/3.4.1/schema_update_after.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql b/application/src/main/data/upgrade/3.4.1/schema_update_before.sql index 27f772aba3..0820fde769 100644 --- a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.4.1/schema_update_before.sql @@ -1,5 +1,5 @@ -- --- Copyright © 2016-2022 The Thingsboard Authors +-- Copyright © 2016-2023 The Thingsboard Authors -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql new file mode 100644 index 0000000000..5965837898 --- /dev/null +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -0,0 +1,612 @@ +-- +-- Copyright © 2016-2023 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +-- USER CREDENTIALS START + +ALTER TABLE user_credentials + ADD COLUMN IF NOT EXISTS additional_info varchar NOT NULL DEFAULT '{}'; + +UPDATE user_credentials + SET additional_info = json_build_object('userPasswordHistory', (u.additional_info::json -> 'userPasswordHistory')) + FROM tb_user u WHERE user_credentials.user_id = u.id AND u.additional_info::jsonb ? 'userPasswordHistory'; + +UPDATE tb_user SET additional_info = tb_user.additional_info::jsonb - 'userPasswordHistory' WHERE additional_info::jsonb ? 'userPasswordHistory'; + +-- USER CREDENTIALS END + +-- ALARM ASSIGN TO USER START + +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT DEFAULT 0; +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; + +CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC); + +-- ALARM ASSIGN TO USER END + +-- ALARM STATUS REFACTORING START + +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS acknowledged boolean; +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS cleared boolean; + +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS status varchar; -- to avoid failure of the subsequent upgrade. +UPDATE alarm SET acknowledged = true, cleared = true WHERE status = 'CLEARED_ACK'; +UPDATE alarm SET acknowledged = true, cleared = false WHERE status = 'ACTIVE_ACK'; +UPDATE alarm SET acknowledged = false, cleared = true WHERE status = 'CLEARED_UNACK'; +UPDATE alarm SET acknowledged = false, cleared = false WHERE status = 'ACTIVE_UNACK'; + +-- Drop index by 'status' column and replace with new one that has only active alarms; +DROP INDEX IF EXISTS idx_alarm_originator_alarm_type_active; +CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active + ON alarm USING btree (originator_id, type) WHERE cleared = false; + +-- Cover index by alarm type to optimize propagated alarm queries; +DROP INDEX IF EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id; +CREATE INDEX IF NOT EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id ON entity_alarm +USING btree (tenant_id, entity_id, alarm_type, created_time DESC) INCLUDE(alarm_id); + +DROP INDEX IF EXISTS idx_alarm_tenant_status_created_time; +ALTER TABLE alarm DROP COLUMN IF EXISTS status; + +-- Update old alarms and set their state to clear, if there are newer alarms. +UPDATE alarm a +SET cleared = TRUE +WHERE cleared = FALSE + AND id != (SELECT l.id + FROM alarm l + WHERE l.tenant_id = a.tenant_id + AND l.originator_id = a.originator_id + AND l.type = a.type + ORDER BY l.created_time DESC, l.id + LIMIT 1); + +-- ALARM STATUS REFACTORING END + +-- ALARM COMMENTS START + +CREATE TABLE IF NOT EXISTS alarm_comment ( + id uuid NOT NULL, + created_time bigint NOT NULL, + alarm_id uuid NOT NULL, + user_id uuid, + type varchar(255) NOT NULL, + comment varchar(10000), + CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE +) PARTITION BY RANGE (created_time); +CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); + +-- ALARM COMMENTS END + +-- NOTIFICATIONS START + +CREATE TABLE IF NOT EXISTS notification_target ( + id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + configuration VARCHAR(10000) NOT NULL, + CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) +); +CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC); + +CREATE TABLE IF NOT EXISTS notification_template ( + id UUID NOT NULL CONSTRAINT notification_template_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + notification_type VARCHAR(50) NOT NULL, + configuration VARCHAR(10000) NOT NULL, + CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) +); +CREATE INDEX IF NOT EXISTS idx_notification_template_tenant_id_created_time ON notification_template(tenant_id, created_time DESC); + +CREATE TABLE IF NOT EXISTS notification_rule ( + id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), + trigger_type VARCHAR(50) NOT NULL, + trigger_config VARCHAR(1000) NOT NULL, + recipients_config VARCHAR(10000) NOT NULL, + additional_config VARCHAR(255), + CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) +); +CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_trigger_type_created_time ON notification_rule(tenant_id, trigger_type, created_time DESC); + +CREATE TABLE IF NOT EXISTS notification_request ( + id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + targets VARCHAR(10000) NOT NULL, + template_id UUID, + template VARCHAR(10000), + info VARCHAR(1000), + additional_config VARCHAR(1000), + originator_entity_id UUID, + originator_entity_type VARCHAR(32), + rule_id UUID NULL, + status VARCHAR(32), + stats VARCHAR(10000) +); +CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_user_created_time ON notification_request(tenant_id, created_time DESC) + WHERE originator_entity_type = 'USER'; +CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id) + WHERE originator_entity_type = 'ALARM'; +CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status) + WHERE status = 'SCHEDULED'; + +CREATE TABLE IF NOT EXISTS notification ( + id UUID NOT NULL, + created_time BIGINT NOT NULL, + request_id UUID NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE, + recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, + type VARCHAR(50) NOT NULL, + subject VARCHAR(255), + body VARCHAR(1000) NOT NULL, + additional_config VARCHAR(1000), + status VARCHAR(32) +) PARTITION BY RANGE (created_time); +CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); +CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); + +-- NOTIFICATIONS END + +ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); + +CREATE TABLE IF NOT EXISTS user_settings ( + user_id uuid NOT NULL, + type VARCHAR(50) NOT NULL, + settings varchar(10000), + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE, + CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type) +); +-- ALARM INFO VIEW + +DROP VIEW IF EXISTS alarm_info CASCADE; +CREATE VIEW alarm_info AS +SELECT a.*, +(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK' + WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK' + WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK' + WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status, +COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) + WHEN a.originator_type = 1 THEN (select title from customer where id = a.originator_id) + WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) + WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) + WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id) + WHEN a.originator_type = 5 THEN (select name from device where id = a.originator_id) + WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) + WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) + WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) + WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END + , 'Deleted') originator_name, +COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) + WHEN a.originator_type = 1 THEN (select COALESCE(NULLIF(title, ''), email) from customer where id = a.originator_id) + WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) + WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) + WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id) + WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id) + WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) + WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) + WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) + WHEN a.originator_type = 18 THEN (select COALESCE(NULLIF(label, ''), name) from edge where id = a.originator_id) END + , 'Deleted') as originator_label, +u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email +FROM alarm a +LEFT JOIN tb_user u ON u.id = a.assignee_id; + +-- ALARM INFO VIEW END + +-- ALARM FUNCTIONS START + +DROP FUNCTION IF EXISTS create_or_update_active_alarm; +CREATE OR REPLACE FUNCTION create_or_update_active_alarm( + t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint, + a_o_id uuid, a_o_type integer, a_type varchar, + a_severity varchar, a_start_ts bigint, a_end_ts bigint, + a_details varchar, + a_propagate boolean, a_propagate_to_owner boolean, + a_propagate_to_tenant boolean, a_propagation_types varchar, + a_creation_enabled boolean) + RETURNS varchar + LANGUAGE plpgsql +AS +$$ +DECLARE + null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid; + existing alarm; + result alarm_info; + row_count integer; +BEGIN + SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE; + IF existing.id IS NULL THEN + IF a_creation_enabled = FALSE THEN + RETURN json_build_object('success', false)::text; + END IF; + IF c_id = null_id THEN + c_id = NULL; + end if; + INSERT INTO alarm + (tenant_id, customer_id, id, created_time, + originator_id, originator_type, type, + severity, start_ts, end_ts, + additional_info, + propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types, + acknowledged, ack_ts, + cleared, clear_ts, + assignee_id, assign_ts) + VALUES + (t_id, c_id, a_id, a_created_ts, + a_o_id, a_o_type, a_type, + a_severity, a_start_ts, a_end_ts, + a_details, + a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types, + false, 0, false, 0, NULL, 0); + SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; + RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text; + ELSE + UPDATE alarm a + SET severity = a_severity, + start_ts = a_start_ts, + end_ts = a_end_ts, + additional_info = a_details, + propagate = a_propagate, + propagate_to_owner = a_propagate_to_owner, + propagate_to_tenant = a_propagate_to_tenant, + propagate_relation_types = a_propagation_types + WHERE a.id = existing.id + AND a.tenant_id = t_id + AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details + OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR + propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types); + GET DIAGNOSTICS row_count = ROW_COUNT; + SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id; + IF row_count > 0 THEN + RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text; + ELSE + RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text; + END IF; + END IF; +END +$$; + +DROP FUNCTION IF EXISTS update_alarm; +CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint, + a_details varchar, + a_propagate boolean, a_propagate_to_owner boolean, + a_propagate_to_tenant boolean, a_propagation_types varchar) + RETURNS varchar + LANGUAGE plpgsql +AS +$$ +DECLARE + existing alarm; + result alarm_info; + row_count integer; +BEGIN + SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; + IF existing IS NULL THEN + RETURN json_build_object('success', false)::text; + END IF; + UPDATE alarm a + SET severity = a_severity, + start_ts = a_start_ts, + end_ts = a_end_ts, + additional_info = a_details, + propagate = a_propagate, + propagate_to_owner = a_propagate_to_owner, + propagate_to_tenant = a_propagate_to_tenant, + propagate_relation_types = a_propagation_types + WHERE a.id = a_id + AND a.tenant_id = t_id + AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details + OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR + propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types); + GET DIAGNOSTICS row_count = ROW_COUNT; + SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; + IF row_count > 0 THEN + RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text; + ELSE + RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text; + END IF; +END +$$; + +DROP FUNCTION IF EXISTS acknowledge_alarm; +CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint) + RETURNS varchar + LANGUAGE plpgsql +AS +$$ +DECLARE + existing alarm; + result alarm_info; + modified boolean = FALSE; +BEGIN + SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; + IF existing IS NULL THEN + RETURN json_build_object('success', false)::text; + END IF; + + IF NOT (existing.acknowledged) THEN + modified = TRUE; + UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; + END IF; + SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; + RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text; +END +$$; + +DROP FUNCTION IF EXISTS clear_alarm; +CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar) + RETURNS varchar + LANGUAGE plpgsql +AS +$$ +DECLARE + existing alarm; + result alarm_info; + cleared boolean = FALSE; +BEGIN + SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; + IF existing IS NULL THEN + RETURN json_build_object('success', false)::text; + END IF; + IF NOT(existing.cleared) THEN + cleared = TRUE; + UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id; + END IF; + SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; + RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text; +END +$$; + +DROP FUNCTION IF EXISTS assign_alarm; +CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint) + RETURNS varchar + LANGUAGE plpgsql +AS +$$ +DECLARE + existing alarm; + result alarm_info; + modified boolean = FALSE; +BEGIN + SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; + IF existing IS NULL THEN + RETURN json_build_object('success', false)::text; + END IF; + IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN + modified = TRUE; + UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; + END IF; + SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; + RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text; +END +$$; + +DROP FUNCTION IF EXISTS unassign_alarm; +CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint) + RETURNS varchar + LANGUAGE plpgsql +AS +$$ +DECLARE + existing alarm; + result alarm_info; + modified boolean = FALSE; +BEGIN + SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; + IF existing IS NULL THEN + RETURN json_build_object('success', false)::text; + END IF; + IF existing.assignee_id IS NOT NULL THEN + modified = TRUE; + UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; + END IF; + SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; + RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text; +END +$$; + +-- ALARM FUNCTIONS END + +-- TTL DROP PARTITIONS FUNCTIONS UPDATE START + +DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); +DROP FUNCTION IF EXISTS get_partition_by_max_ttl_date; + +CREATE OR REPLACE FUNCTION get_partition_by_system_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS +$$ +BEGIN + CASE + WHEN partition_type = 'DAYS' THEN + partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM') || '_' || to_char(date, 'dd'); + WHEN partition_type = 'MONTHS' THEN + partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); + WHEN partition_type = 'YEARS' THEN + partition := 'ts_kv_' || to_char(date, 'yyyy'); + ELSE + partition := NULL; + END CASE; + IF partition IS NOT NULL THEN + IF NOT EXISTS(SELECT + FROM pg_tables + WHERE schemaname = 'public' + AND tablename = partition) THEN + partition := NULL; + RAISE NOTICE 'Failed to found partition by ttl'; + END IF; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE PROCEDURE drop_partitions_by_system_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) + LANGUAGE plpgsql AS +$$ +DECLARE + date timestamp; + partition_by_max_ttl_date varchar; + partition_by_max_ttl_month varchar; + partition_by_max_ttl_day varchar; + partition_by_max_ttl_year varchar; + partition varchar; + partition_year integer; + partition_month integer; + partition_day integer; + +BEGIN + if system_ttl IS NOT NULL AND system_ttl > 0 THEN + date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - system_ttl); + partition_by_max_ttl_date := get_partition_by_system_ttl_date(partition_type, date); + RAISE NOTICE 'Date by max ttl: %', date; + RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; + IF partition_by_max_ttl_date IS NOT NULL THEN + CASE + WHEN partition_type = 'DAYS' THEN + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); + WHEN partition_type = 'MONTHS' THEN + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + ELSE + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + END CASE; + IF partition_by_max_ttl_year IS NULL THEN + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; + ELSE + IF partition_type = 'YEARS' THEN + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END LOOP; + ELSE + IF partition_type = 'MONTHS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_year = partition_by_max_ttl_year::integer THEN + IF partition_month >= partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END LOOP; + END IF; + ELSE + IF partition_type = 'DAYS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + IF partition_by_max_ttl_day IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_month > partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_month < partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_day := SPLIT_PART(partition, '_', 5)::integer; + IF partition_day >= partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_day < partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END LOOP; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; +END +$$; + +-- TTL DROP PARTITIONS FUNCTIONS UPDATE END \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java index b4a0e019b6..b780f0e8cd 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java index a9c462ee05..0fe6915877 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 8b19a6954c..26eab30d5d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,9 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.script.api.tbel.TbelInvokeService; @@ -50,6 +52,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; @@ -67,6 +70,10 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -82,6 +89,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; @@ -89,6 +97,7 @@ import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; +import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -278,6 +287,10 @@ public class ActorSystemContext { @Getter private AlarmSubscriptionService alarmService; + @Autowired + @Getter + private AlarmCommentService alarmCommentService; + @Autowired @Getter private JsInvokeService jsInvokeService; @@ -302,6 +315,10 @@ public class ActorSystemContext { @Getter private ExternalCallExecutorService externalCallExecutorService; + @Autowired + @Getter + private NotificationExecutorService notificationExecutor; + @Autowired @Getter private SharedEventLoopGroupService sharedEventLoopGroupService; @@ -318,6 +335,34 @@ public class ActorSystemContext { @Getter private SmsSenderFactory smsSenderFactory; + @Autowired + @Getter + private NotificationCenter notificationCenter; + + @Autowired + @Getter + private NotificationRuleProcessor notificationRuleProcessor; + + @Autowired + @Getter + private NotificationTargetService notificationTargetService; + + @Autowired + @Getter + private NotificationTemplateService notificationTemplateService; + + @Autowired + @Getter + private NotificationRequestService notificationRequestService; + + @Autowired + @Getter + private NotificationRuleService notificationRuleService; + + @Autowired + @Getter + private SlackService slackService; + @Lazy @Autowired(required = false) @Getter diff --git a/application/src/main/java/org/thingsboard/server/actors/TbEntityTypeActorIdPredicate.java b/application/src/main/java/org/thingsboard/server/actors/TbEntityTypeActorIdPredicate.java index 9a8232ebdb..ce2c239b41 100644 --- a/application/src/main/java/org/thingsboard/server/actors/TbEntityTypeActorIdPredicate.java +++ b/application/src/main/java/org/thingsboard/server/actors/TbEntityTypeActorIdPredicate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index eeb6d8e82e..fb6fbbdff2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java b/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java index 772c9ae5b5..14bb93898e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java index a0f49ba62f..d5833e0c07 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java index 7a369a57e9..c5563dfaa8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 8a365b4e56..0c3589a077 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,7 +116,7 @@ import java.util.stream.Collectors; * @author Andrew Shvayka */ @Slf4j -class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { +public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { static final String SESSION_TIMEOUT_MESSAGE = "session timeout!"; final TenantId tenantId; diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java index 01e4765190..07fe24b272 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java index a26fe90d65..2a84a628ef 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java index 6815aee675..e74baf8b3d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java index d003656874..f876408d24 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java index c0a68c178d..f5d9e0595c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index ba1c4e9796..59d633540e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.bouncycastle.util.Arrays; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService; import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; @@ -35,6 +36,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; import org.thingsboard.server.actors.ActorSystemContext; @@ -71,6 +73,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgProcessingStackItem; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; @@ -85,6 +88,10 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -104,6 +111,7 @@ import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -470,6 +478,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getExternalCallExecutorService(); } + @Override + public ListeningExecutor getNotificationExecutor() { + return mainCtx.getNotificationExecutor(); + } + @Override @Deprecated public ScriptEngine createJsScriptEngine(String script, String... argNames) { @@ -591,6 +604,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getAlarmService(); } + @Override + public AlarmCommentService getAlarmCommentService() { + return mainCtx.getAlarmCommentService(); + } + @Override public RuleChainService getRuleChainService() { return mainCtx.getRuleChainService(); @@ -679,6 +697,36 @@ class DefaultTbContext implements TbContext { return mainCtx.getSmsSenderFactory(); } + @Override + public NotificationCenter getNotificationCenter() { + return mainCtx.getNotificationCenter(); + } + + @Override + public NotificationTargetService getNotificationTargetService() { + return mainCtx.getNotificationTargetService(); + } + + @Override + public NotificationTemplateService getNotificationTemplateService() { + return mainCtx.getNotificationTemplateService(); + } + + @Override + public NotificationRequestService getNotificationRequestService() { + return mainCtx.getNotificationRequestService(); + } + + @Override + public NotificationRuleService getNotificationRuleService() { + return mainCtx.getNotificationRuleService(); + } + + @Override + public SlackService getSlackService() { + return mainCtx.getSlackService(); + } + @Override public RuleEngineRpcService getRpcService() { return mainCtx.getTbRuleEngineDeviceRpcService(); @@ -788,6 +836,12 @@ class DefaultTbContext implements TbContext { return metaData; } + + @Override + public void schedule(Runnable runnable, long delay, TimeUnit timeUnit) { + mainCtx.getScheduler().schedule(runnable, delay, timeUnit); + } + @Override public void checkTenantEntity(EntityId entityId) { if (!this.getTenantId().equals(TenantIdLoader.findTenantId(this, entityId))) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java index ff9e21dedd..da45f0f53d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorId; import org.thingsboard.server.actors.TbEntityActorId; -import org.thingsboard.server.actors.service.ComponentActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; @@ -30,7 +29,7 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; -public class RuleChainActor extends ComponentActor { +public class RuleChainActor extends RuleEngineComponentActor { private final RuleChain ruleChain; @@ -101,6 +100,16 @@ public class RuleChainActor extends ComponentActor> extends ComponentActor { + + public RuleEngineComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) { + super(systemContext, tenantId, id); + } + + @Override + protected void logLifecycleEvent(ComponentLifecycleEvent event, Exception e) { + super.logLifecycleEvent(event, e); + if (e instanceof TbRuleNodeUpdateException || (event == ComponentLifecycleEvent.STARTED && e != null)) { + return; + } + processNotificationRule(event, e); + } + + @Override + public void destroy(TbActorStopReason stopReason, Throwable cause) { + super.destroy(stopReason, cause); + if (stopReason == TbActorStopReason.INIT_FAILED && cause != null) { + processNotificationRule(ComponentLifecycleEvent.STARTED, cause); + } + } + + private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) { + systemContext.getNotificationRuleProcessor().process(RuleEngineComponentLifecycleEventTrigger.builder() + .tenantId(tenantId) + .ruleChainId(getRuleChainId()) + .ruleChainName(getRuleChainName()) + .componentId(id) + .componentName(processor.getComponentName()) + .eventType(event) + .error(e) + .build()); + } + + protected abstract RuleChainId getRuleChainId(); + + protected abstract String getRuleChainName(); + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java index f5847f764b..e8dec535f6 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorId; import org.thingsboard.server.actors.TbEntityActorId; -import org.thingsboard.server.actors.service.ComponentActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -32,7 +31,7 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; @Slf4j -public class RuleNodeActor extends ComponentActor { +public class RuleNodeActor extends RuleEngineComponentActor { private final String ruleChainName; private final RuleChainId ruleChainId; @@ -133,6 +132,16 @@ public class RuleNodeActor extends ComponentActor matchers.antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")) - .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) - .requestCache().disable() - .securityContext().disable() - .sessionManagement().disable(); - return http.build(); + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); } @Bean diff --git a/application/src/main/java/org/thingsboard/server/config/WebConfig.java b/application/src/main/java/org/thingsboard/server/config/WebConfig.java index 012d57f3bd..65396576c1 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebConfig.java +++ b/application/src/main/java/org/thingsboard/server/config/WebConfig.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java index 13b13123e6..9a6ffb0e32 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.thingsboard.server.config; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @@ -40,11 +42,15 @@ import java.util.Map; @Configuration @TbCoreComponent @EnableWebSocket +@RequiredArgsConstructor +@Slf4j public class WebSocketConfiguration implements WebSocketConfigurer { public static final String WS_PLUGIN_PREFIX = "/api/ws/plugins/"; private static final String WS_PLUGIN_MAPPING = WS_PLUGIN_PREFIX + "**"; + private final WebSocketHandler wsHandler; + @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); @@ -55,7 +61,11 @@ public class WebSocketConfiguration implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(wsHandler(), WS_PLUGIN_MAPPING).setAllowedOriginPatterns("*") + if (!(wsHandler instanceof TbWebSocketHandler)) { + log.error("TbWebSocketHandler expected but [{}] provided", wsHandler); + throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided"); + } + registry.addHandler(wsHandler, WS_PLUGIN_MAPPING).setAllowedOriginPatterns("*") .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() { @Override @@ -82,11 +92,6 @@ public class WebSocketConfiguration implements WebSocketConfigurer { }); } - @Bean - public WebSocketHandler wsHandler() { - return new TbWebSocketHandler(); - } - protected SecurityUser getCurrentUser() throws ThingsboardException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) { diff --git a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java index e5a217893e..48172b368c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; +import org.thingsboard.server.exception.ToErrorResponseEntity; import javax.annotation.Nullable; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 29f32b8ac9..5f47bcde2c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,29 +21,41 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.FeaturesInfo; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.SystemInfo; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo; -import org.thingsboard.server.common.data.security.model.JwtSettings; -import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.permission.Operation; @@ -51,43 +63,30 @@ import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService; +import org.thingsboard.server.service.system.SystemInfoService; import org.thingsboard.server.service.update.UpdateService; -import static org.thingsboard.server.controller.ControllerConstants.*; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @RestController @TbCoreComponent @RequestMapping("/api/admin") +@RequiredArgsConstructor public class AdminController extends BaseController { - @Autowired - private MailService mailService; - - @Autowired - private SmsService smsService; - - @Autowired - private AdminSettingsService adminSettingsService; - - @Autowired - private SystemSecurityService systemSecurityService; - + private final MailService mailService; + private final SmsService smsService; + private final AdminSettingsService adminSettingsService; + private final SystemSecurityService systemSecurityService; @Lazy - @Autowired - private JwtSettingsService jwtSettingsService; - + private final JwtSettingsService jwtSettingsService; @Lazy - @Autowired - private JwtTokenFactory tokenFactory; - - @Autowired - private EntitiesVersionControlService versionControlService; - - @Autowired - private TbAutoCommitSettingsService autoCommitSettingsService; - - @Autowired - private UpdateService updateService; + private final JwtTokenFactory tokenFactory; + private final EntitiesVersionControlService versionControlService; + private final TbAutoCommitSettingsService autoCommitSettingsService; + private final UpdateService updateService; + private final SystemInfoService systemInfoService; @ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)", notes = "Get the Administration Settings object using specified string key. Referencing non-existing key will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) @@ -97,19 +96,14 @@ public class AdminController extends BaseController { public AdminSettings getAdminSettings( @ApiParam(value = "A string value of the key (e.g. 'general' or 'mail').") @PathVariable("key") String key) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); - AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key), "No Administration settings found for key: " + key); - if (adminSettings.getKey().equals("mail")) { - ((ObjectNode) adminSettings.getJsonValue()).remove("password"); - } - return adminSettings; - } catch (Exception e) { - throw handleException(e); + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key), "No Administration settings found for key: " + key); + if (adminSettings.getKey().equals("mail")) { + ((ObjectNode) adminSettings.getJsonValue()).remove("password"); } + return adminSettings; } - @ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)", notes = "Creates or Updates the Administration Settings. Platform generates random Administration Settings Id during settings creation. " + "The Administration Settings Id will be present in the response. Specify the Administration Settings Id when you would like to update the Administration Settings. " + @@ -120,20 +114,16 @@ public class AdminController extends BaseController { public AdminSettings saveAdminSettings( @ApiParam(value = "A JSON value representing the Administration Settings.") @RequestBody AdminSettings adminSettings) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); - adminSettings.setTenantId(getTenantId()); - adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); - if (adminSettings.getKey().equals("mail")) { - mailService.updateMailConfiguration(); - ((ObjectNode) adminSettings.getJsonValue()).remove("password"); - } else if (adminSettings.getKey().equals("sms")) { - smsService.updateSmsConfiguration(); - } - return adminSettings; - } catch (Exception e) { - throw handleException(e); + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); + adminSettings.setTenantId(getTenantId()); + adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); + if (adminSettings.getKey().equals("mail")) { + mailService.updateMailConfiguration(); + ((ObjectNode) adminSettings.getJsonValue()).remove("password"); + } else if (adminSettings.getKey().equals("sms")) { + smsService.updateSmsConfiguration(); } + return adminSettings; } @ApiOperation(value = "Get the Security Settings object", @@ -142,12 +132,8 @@ public class AdminController extends BaseController { @RequestMapping(value = "/securitySettings", method = RequestMethod.GET) @ResponseBody public SecuritySettings getSecuritySettings() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); - return checkNotNull(systemSecurityService.getSecuritySettings(TenantId.SYS_TENANT_ID)); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + return checkNotNull(systemSecurityService.getSecuritySettings(TenantId.SYS_TENANT_ID)); } @ApiOperation(value = "Update Security Settings (saveSecuritySettings)", @@ -158,13 +144,9 @@ public class AdminController extends BaseController { public SecuritySettings saveSecuritySettings( @ApiParam(value = "A JSON value representing the Security Settings.") @RequestBody SecuritySettings securitySettings) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); - securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings)); - return securitySettings; - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); + securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings)); + return securitySettings; } @ApiOperation(value = "Get the JWT Settings object (getJwtSettings)", @@ -174,12 +156,8 @@ public class AdminController extends BaseController { @RequestMapping(value = "/jwtSettings", method = RequestMethod.GET) @ResponseBody public JwtSettings getJwtSettings() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); - return checkNotNull(jwtSettingsService.getJwtSettings()); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + return checkNotNull(jwtSettingsService.getJwtSettings()); } @ApiOperation(value = "Update JWT Settings (saveJwtSettings)", @@ -191,14 +169,10 @@ public class AdminController extends BaseController { public JwtPair saveJwtSettings( @ApiParam(value = "A JSON value representing the JWT Settings.") @RequestBody JwtSettings jwtSettings) throws ThingsboardException { - try { - SecurityUser securityUser = getCurrentUser(); - accessControlService.checkPermission(securityUser, Resource.ADMIN_SETTINGS, Operation.WRITE); - checkNotNull(jwtSettingsService.saveJwtSettings(jwtSettings)); - return tokenFactory.createTokenPair(securityUser); - } catch (Exception e) { - throw handleException(e); - } + SecurityUser securityUser = getCurrentUser(); + accessControlService.checkPermission(securityUser, Resource.ADMIN_SETTINGS, Operation.WRITE); + checkNotNull(jwtSettingsService.saveJwtSettings(jwtSettings)); + return tokenFactory.createTokenPair(securityUser); } @ApiOperation(value = "Send test email (sendTestMail)", @@ -209,19 +183,15 @@ public class AdminController extends BaseController { public void sendTestMail( @ApiParam(value = "A JSON value representing the Mail Settings.") @RequestBody AdminSettings adminSettings) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); - adminSettings = checkNotNull(adminSettings); - if (adminSettings.getKey().equals("mail")) { - if (!adminSettings.getJsonValue().has("password")) { - AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail")); - ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); - } - String email = getCurrentUser().getEmail(); - mailService.sendTestMail(adminSettings.getJsonValue(), email); + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + adminSettings = checkNotNull(adminSettings); + if (adminSettings.getKey().equals("mail")) { + if (!adminSettings.getJsonValue().has("password")) { + AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail")); + ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); } - } catch (Exception e) { - throw handleException(e); + String email = getCurrentUser().getEmail(); + mailService.sendTestMail(adminSettings.getJsonValue(), email); } } @@ -233,12 +203,8 @@ public class AdminController extends BaseController { public void sendTestSms( @ApiParam(value = "A JSON value representing the Test SMS request.") @RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); - smsService.sendTestSms(testSmsRequest); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + smsService.sendTestSms(testSmsRequest); } @ApiOperation(value = "Get repository settings (getRepositorySettings)", @@ -246,16 +212,12 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/repositorySettings") public RepositorySettings getRepositorySettings() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); - RepositorySettings versionControlSettings = checkNotNull(versionControlService.getVersionControlSettings(getTenantId())); - versionControlSettings.setPassword(null); - versionControlSettings.setPrivateKey(null); - versionControlSettings.setPrivateKeyPassword(null); - return versionControlSettings; - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + RepositorySettings versionControlSettings = checkNotNull(versionControlService.getVersionControlSettings(getTenantId())); + versionControlSettings.setPassword(null); + versionControlSettings.setPrivateKey(null); + versionControlSettings.setPrivateKeyPassword(null); + return versionControlSettings; } @ApiOperation(value = "Check repository settings exists (repositorySettingsExists)", @@ -263,12 +225,8 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/repositorySettings/exists") public Boolean repositorySettingsExists() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); - return versionControlService.getVersionControlSettings(getTenantId()) != null; - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return versionControlService.getVersionControlSettings(getTenantId()) != null; } @PreAuthorize("hasAuthority('TENANT_ADMIN')") @@ -309,30 +267,21 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/repositorySettings", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public DeferredResult deleteRepositorySettings() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); - return wrapFuture(versionControlService.deleteVersionControlSettings(getTenantId())); - } catch (Exception e) { - throw handleException(e); - } + public DeferredResult deleteRepositorySettings() throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); + return wrapFuture(versionControlService.deleteVersionControlSettings(getTenantId())); } - @ApiOperation(value = "Check repository access (checkRepositoryAccess)", notes = "Attempts to check repository access. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/repositorySettings/checkAccess", method = RequestMethod.POST) public DeferredResult checkRepositoryAccess( @ApiParam(value = "A JSON value representing the Repository Settings.") - @RequestBody RepositorySettings settings) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); - settings = checkNotNull(settings); - return wrapFuture(versionControlService.checkVersionControlAccess(getTenantId(), settings)); - } catch (Exception e) { - throw handleException(e); - } + @RequestBody RepositorySettings settings) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + settings = checkNotNull(settings); + return wrapFuture(versionControlService.checkVersionControlAccess(getTenantId(), settings)); } @ApiOperation(value = "Get auto commit settings (getAutoCommitSettings)", @@ -340,12 +289,8 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/autoCommitSettings") public AutoCommitSettings getAutoCommitSettings() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); - return checkNotNull(autoCommitSettingsService.get(getTenantId())); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return checkNotNull(autoCommitSettingsService.get(getTenantId())); } @ApiOperation(value = "Check auto commit settings exists (autoCommitSettingsExists)", @@ -353,12 +298,8 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/autoCommitSettings/exists") public Boolean autoCommitSettingsExists() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); - return autoCommitSettingsService.get(getTenantId()) != null; - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return autoCommitSettingsService.get(getTenantId()) != null; } @ApiOperation(value = "Creates or Updates the auto commit settings (saveAutoCommitSettings)", @@ -377,12 +318,8 @@ public class AdminController extends BaseController { @RequestMapping(value = "/autoCommitSettings", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteAutoCommitSettings() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); - autoCommitSettingsService.delete(getTenantId()); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); + autoCommitSettingsService.delete(getTenantId()); } @ApiOperation(value = "Check for new Platform Releases (checkUpdates)", @@ -392,11 +329,27 @@ public class AdminController extends BaseController { @RequestMapping(value = "/updates", method = RequestMethod.GET) @ResponseBody public UpdateMessage checkUpdates() throws ThingsboardException { - try { - return updateService.checkUpdates(); - } catch (Exception e) { - throw handleException(e); - } + return updateService.checkUpdates(); + } + + @ApiOperation(value = "Get system info (getSystemInfo)", + notes = "Get main information about system. " + + SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @RequestMapping(value = "/systemInfo", method = RequestMethod.GET) + @ResponseBody + public SystemInfo getSystemInfo() throws ThingsboardException { + return systemInfoService.getSystemInfo(); + } + + @ApiOperation(value = "Get features info (getFeaturesInfo)", + notes = "Get information about enabled/disabled features. " + + SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @RequestMapping(value = "/featuresInfo", method = RequestMethod.GET) + @ResponseBody + public FeaturesInfo getFeaturesInfo() { + return systemInfoService.getFeaturesInfo(); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java new file mode 100644 index 0000000000..92b2cb923f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java @@ -0,0 +1,124 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AlarmCommentId; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; +import org.thingsboard.server.service.security.permission.Operation; + +import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; + +@RestController +@TbCoreComponent +@RequiredArgsConstructor +@RequestMapping("/api") +public class AlarmCommentController extends BaseController { + public static final String ALARM_ID = "alarmId"; + public static final String ALARM_COMMENT_ID = "commentId"; + + private final TbAlarmCommentService tbAlarmCommentService; + + @ApiOperation(value = "Create or update Alarm Comment ", + notes = "Creates or Updates the Alarm Comment. " + + "When creating comment, platform generates Alarm Comment Id as " + UUID_WIKI_LINK + + "The newly created Alarm Comment id will be present in the response. Specify existing Alarm Comment id to update the alarm. " + + "Referencing non-existing Alarm Comment Id will cause 'Not Found' error. " + + "\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " + + "\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH + , produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST) + @ResponseBody + public AlarmComment saveAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException { + checkParameter(ALARM_ID, strAlarmId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + alarmComment.setAlarmId(alarmId); + return tbAlarmCommentService.saveAlarmComment(alarm, alarmComment, getCurrentUser()); + } + + @ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)", + notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE) + @ResponseBody + public void deleteAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = ALARM_COMMENT_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_COMMENT_ID) String strCommentId) throws ThingsboardException { + checkParameter(ALARM_ID, strAlarmId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + + AlarmCommentId alarmCommentId = new AlarmCommentId(toUUID(strCommentId)); + AlarmComment alarmComment = checkAlarmCommentId(alarmCommentId, alarmId); + tbAlarmCommentService.deleteAlarmComment(alarm, alarmComment, getCurrentUser()); + } + + @ApiOperation(value = "Get Alarm comments (getAlarmComments)", + notes = "Returns a page of alarm comments for specified alarm. " + + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET) + @ResponseBody + public PageData getAlarmComments( + @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION, required = true) + @PathVariable(ALARM_ID) String strAlarmId, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder + ) throws Exception { + checkParameter(ALARM_ID, strAlarmId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder); + return checkNotNull(alarmCommentService.findAlarmComments(alarm.getTenantId(), alarmId, pageLink)); + } +} diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 858a7ee3c4..59ed65c6c9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -41,16 +42,24 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ALARM_INFO_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.ASSIGNEE_ID; +import static org.thingsboard.server.controller.ControllerConstants.ASSIGN_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE; @@ -79,6 +88,7 @@ public class AlarmController extends BaseController { private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK"; private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value"; private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK"; + private static final String ALARM_QUERY_ASSIGNEE_DESCRIPTION = "A string value representing the assignee user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on of next alarm fields: type, severity or status"; private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; @@ -93,12 +103,8 @@ public class AlarmController extends BaseController { public Alarm getAlarmById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); - try { - AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - return checkAlarmId(alarmId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + return checkAlarmId(alarmId, Operation.READ); } @ApiOperation(value = "Get Alarm Info (getAlarmInfoById)", @@ -110,15 +116,11 @@ public class AlarmController extends BaseController { public AlarmInfo getAlarmInfoById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); - try { - AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - return checkAlarmInfoId(alarmId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + return checkAlarmInfoId(alarmId, Operation.READ); } - @ApiOperation(value = "Create or update Alarm (saveAlarm)", + @ApiOperation(value = "Create or Update Alarm (saveAlarm)", notes = "Creates or Updates the Alarm. " + "When creating alarm, platform generates Alarm Id as " + UUID_WIKI_LINK + "The newly created Alarm id will be present in the response. Specify existing Alarm id to update the alarm. " + @@ -135,7 +137,12 @@ public class AlarmController extends BaseController { @ResponseBody public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { alarm.setTenantId(getTenantId()); + checkNotNull(alarm.getOriginator()); checkEntity(alarm.getId(), alarm, Resource.ALARM); + checkEntityId(alarm.getOriginator(), Operation.READ); + if (alarm.getAssigneeId() != null) { + checkUserId(alarm.getAssigneeId(), Operation.READ); + } return tbAlarmService.save(alarm, getCurrentUser()); } @@ -158,11 +165,12 @@ public class AlarmController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { + public AlarmInfo ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); - tbAlarmService.ack(alarm, getCurrentUser()).get(); + //TODO: return correct error code if the alarm is not found or already cleared + return tbAlarmService.ack(alarm, getCurrentUser()); } @ApiOperation(value = "Clear Alarm (clearAlarm)", @@ -172,11 +180,50 @@ public class AlarmController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { + public AlarmInfo clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); - tbAlarmService.clear(alarm, getCurrentUser()).get(); + //TODO: return correct error code if the alarm is not found or already cleared + return tbAlarmService.clear(alarm, getCurrentUser()); + } + + @ApiOperation(value = "Assign/Reassign Alarm (assignAlarm)", + notes = "Assign the Alarm. " + + "Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " + + "(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " + + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public Alarm assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId, + @ApiParam(value = ASSIGN_ID_PARAM_DESCRIPTION) + @PathVariable(ASSIGNEE_ID) String strAssigneeId + ) throws Exception { + checkParameter(ALARM_ID, strAlarmId); + checkParameter(ASSIGNEE_ID, strAssigneeId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); + checkUserId(assigneeId, Operation.READ); + return tbAlarmService.assign(alarm, assigneeId, System.currentTimeMillis(), getCurrentUser()); + } + + @ApiOperation(value = "Unassign Alarm (unassignAlarm)", + notes = "Unassign the Alarm. " + + "Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " + + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public Alarm unassignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId + ) throws Exception { + checkParameter(ALARM_ID, strAlarmId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + return tbAlarmService.unassign(alarm, System.currentTimeMillis(), getCurrentUser()); } @ApiOperation(value = "Get Alarms (getAlarms)", @@ -194,6 +241,8 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String searchStatus, @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) @RequestParam(required = false) String status, + @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @RequestParam(required = false) String assigneeId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @@ -210,7 +259,7 @@ public class AlarmController extends BaseController { @RequestParam(required = false) Long endTime, @ApiParam(value = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) @RequestParam(required = false) Boolean fetchOriginator - ) throws ThingsboardException { + ) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); @@ -221,13 +270,13 @@ public class AlarmController extends BaseController { "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } checkEntityId(entityId, Operation.READ); + UserId assigneeUserId = null; + if (assigneeId != null) { + assigneeUserId = new UserId(UUID.fromString(assigneeId)); + } TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - try { - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get()); } @ApiOperation(value = "Get All Alarms (getAllAlarms)", @@ -244,6 +293,8 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String searchStatus, @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) @RequestParam(required = false) String status, + @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @RequestParam(required = false) String assigneeId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @@ -260,23 +311,23 @@ public class AlarmController extends BaseController { @RequestParam(required = false) Long endTime, @ApiParam(value = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) @RequestParam(required = false) Boolean fetchOriginator - ) throws ThingsboardException { + ) throws ThingsboardException, ExecutionException, InterruptedException { AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); if (alarmSearchStatus != null && alarmStatus != null) { throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } + UserId assigneeUserId = null; + if (assigneeId != null) { + assigneeUserId = new UserId(UUID.fromString(assigneeId)); + } TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - try { - if (getCurrentUser().isCustomerUser()) { - return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); - } else { - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); - } - } catch (Exception e) { - throw handleException(e); + if (getCurrentUser().isCustomerUser()) { + return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get()); + } else { + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get()); } } @@ -295,7 +346,9 @@ public class AlarmController extends BaseController { @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) @RequestParam(required = false) String searchStatus, @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) - @RequestParam(required = false) String status + @RequestParam(required = false) String status, + @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @RequestParam(required = false) String assigneeId ) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -307,11 +360,8 @@ public class AlarmController extends BaseController { "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } checkEntityId(entityId, Operation.READ); - try { - return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus); - } catch (Exception e) { - throw handleException(e); - } + return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, + alarmStatus, assigneeId); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 968829b65a..510fefe305 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ import org.thingsboard.server.service.security.permission.Resource; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.ASSET_ID_PARAM_DESCRIPTION; @@ -108,12 +109,8 @@ public class AssetController extends BaseController { public Asset getAssetById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); - try { - AssetId assetId = new AssetId(toUUID(strAssetId)); - return checkAssetId(assetId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + AssetId assetId = new AssetId(toUUID(strAssetId)); + return checkAssetId(assetId, Operation.READ); } @ApiOperation(value = "Get Asset Info (getAssetInfoById)", @@ -127,12 +124,8 @@ public class AssetController extends BaseController { public AssetInfo getAssetInfoById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); - try { - AssetId assetId = new AssetId(toUUID(strAssetId)); - return checkAssetInfoId(assetId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + AssetId assetId = new AssetId(toUUID(strAssetId)); + return checkAssetInfoId(assetId, Operation.READ); } @ApiOperation(value = "Create Or Update Asset (saveAsset)", @@ -228,16 +221,12 @@ public class AssetController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); - } else { - return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink)); } } @@ -262,19 +251,15 @@ public class AssetController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(assetService.findAssetInfosByTenantIdAndType(tenantId, type, pageLink)); - } else if (assetProfileId != null && assetProfileId.length() > 0) { - AssetProfileId profileId = new AssetProfileId(toUUID(assetProfileId)); - return checkNotNull(assetService.findAssetInfosByTenantIdAndAssetProfileId(tenantId, profileId, pageLink)); - } else { - return checkNotNull(assetService.findAssetInfosByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(assetService.findAssetInfosByTenantIdAndType(tenantId, type, pageLink)); + } else if (assetProfileId != null && assetProfileId.length() > 0) { + AssetProfileId profileId = new AssetProfileId(toUUID(assetProfileId)); + return checkNotNull(assetService.findAssetInfosByTenantIdAndAssetProfileId(tenantId, profileId, pageLink)); + } else { + return checkNotNull(assetService.findAssetInfosByTenantId(tenantId, pageLink)); } } @@ -287,12 +272,8 @@ public class AssetController extends BaseController { public Asset getTenantAsset( @ApiParam(value = ASSET_NAME_DESCRIPTION) @RequestParam String assetName) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(assetService.findAssetByTenantIdAndName(tenantId, assetName)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(assetService.findAssetByTenantIdAndName(tenantId, assetName)); } @ApiOperation(value = "Get Customer Assets (getCustomerAssets)", @@ -317,18 +298,14 @@ public class AssetController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); - } else { - return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else { + return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -356,21 +333,17 @@ public class AssetController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); - } else if (assetProfileId != null && assetProfileId.length() > 0) { - AssetProfileId profileId = new AssetProfileId(toUUID(assetProfileId)); - return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(tenantId, customerId, profileId, pageLink)); - } else { - return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else if (assetProfileId != null && assetProfileId.length() > 0) { + AssetProfileId profileId = new AssetProfileId(toUUID(assetProfileId)); + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(tenantId, customerId, profileId, pageLink)); + } else { + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -381,26 +354,22 @@ public class AssetController extends BaseController { @ResponseBody public List getAssetsByIds( @ApiParam(value = "A list of assets ids, separated by comma ','") - @RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException { + @RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException, ExecutionException, InterruptedException { checkArrayParameter("assetIds", strAssetIds); - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - CustomerId customerId = user.getCustomerId(); - List assetIds = new ArrayList<>(); - for (String strAssetId : strAssetIds) { - assetIds.add(new AssetId(toUUID(strAssetId))); - } - ListenableFuture> assets; - if (customerId == null || customerId.isNullUid()) { - assets = assetService.findAssetsByTenantIdAndIdsAsync(tenantId, assetIds); - } else { - assets = assetService.findAssetsByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, assetIds); - } - return checkNotNull(assets.get()); - } catch (Exception e) { - throw handleException(e); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + CustomerId customerId = user.getCustomerId(); + List assetIds = new ArrayList<>(); + for (String strAssetId : strAssetIds) { + assetIds.add(new AssetId(toUUID(strAssetId))); } + ListenableFuture> assets; + if (customerId == null || customerId.isNullUid()) { + assets = assetService.findAssetsByTenantIdAndIdsAsync(tenantId, assetIds); + } else { + assets = assetService.findAssetsByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, assetIds); + } + return checkNotNull(assets.get()); } @ApiOperation(value = "Find related assets (findByQuery)", @@ -410,25 +379,21 @@ public class AssetController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assets", method = RequestMethod.POST) @ResponseBody - public List findByQuery(@RequestBody AssetSearchQuery query) throws ThingsboardException { + public List findByQuery(@RequestBody AssetSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getAssetTypes()); checkEntityId(query.getParameters().getEntityId(), Operation.READ); - try { - List assets = checkNotNull(assetService.findAssetsByQuery(getTenantId(), query).get()); - assets = assets.stream().filter(asset -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, Operation.READ, asset.getId(), asset); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - return assets; - } catch (Exception e) { - throw handleException(e); - } + List assets = checkNotNull(assetService.findAssetsByQuery(getTenantId(), query).get()); + assets = assets.stream().filter(asset -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, Operation.READ, asset.getId(), asset); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + return assets; } @ApiOperation(value = "Get Asset Types (getAssetTypes)", @@ -436,15 +401,11 @@ public class AssetController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset/types", method = RequestMethod.GET) @ResponseBody - public List getAssetTypes() throws ThingsboardException { - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - ListenableFuture> assetTypes = assetService.findAssetTypesByTenantId(tenantId); - return checkNotNull(assetTypes.get()); - } catch (Exception e) { - throw handleException(e); - } + public List getAssetTypes() throws ThingsboardException, ExecutionException, InterruptedException { + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + ListenableFuture> assetTypes = assetService.findAssetTypesByTenantId(tenantId); + return checkNotNull(assetTypes.get()); } @ApiOperation(value = "Assign asset to edge (assignAssetToEdge)", @@ -520,33 +481,29 @@ public class AssetController extends BaseController { @ApiParam(value = "Timestamp. Assets with creation time after it won't be queried") @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.READ); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - PageData nonFilteredResult; - if (type != null && type.trim().length() > 0) { - nonFilteredResult = assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); - } else { - nonFilteredResult = assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); - } - List filteredAssets = nonFilteredResult.getData().stream().filter(asset -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, Operation.READ, asset.getId(), asset); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - PageData filteredResult = new PageData<>(filteredAssets, - nonFilteredResult.getTotalPages(), - nonFilteredResult.getTotalElements(), - nonFilteredResult.hasNext()); - return checkNotNull(filteredResult); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + PageData nonFilteredResult; + if (type != null && type.trim().length() > 0) { + nonFilteredResult = assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); + } else { + nonFilteredResult = assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); } + List filteredAssets = nonFilteredResult.getData().stream().filter(asset -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, Operation.READ, asset.getId(), asset); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + PageData filteredResult = new PageData<>(filteredAssets, + nonFilteredResult.getTotalPages(), + nonFilteredResult.getTotalElements(), + nonFilteredResult.hasNext()); + return checkNotNull(filteredResult); } @ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)", diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java index 2e84dd6634..ebae4169ce 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,12 +78,8 @@ public class AssetProfileController extends BaseController { @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException { checkParameter(ASSET_PROFILE_ID, strAssetProfileId); - try { - AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); - return checkAssetProfileId(assetProfileId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); + return checkAssetProfileId(assetProfileId, Operation.READ); } @ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)", @@ -97,12 +93,8 @@ public class AssetProfileController extends BaseController { @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException { checkParameter(ASSET_PROFILE_ID, strAssetProfileId); - try { - AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); - return new AssetProfileInfo(checkAssetProfileId(assetProfileId, Operation.READ)); - } catch (Exception e) { - throw handleException(e); - } + AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); + return new AssetProfileInfo(checkAssetProfileId(assetProfileId, Operation.READ)); } @ApiOperation(value = "Get Default Asset Profile (getDefaultAssetProfileInfo)", @@ -113,11 +105,7 @@ public class AssetProfileController extends BaseController { @RequestMapping(value = "/assetProfileInfo/default", method = RequestMethod.GET) @ResponseBody public AssetProfileInfo getDefaultAssetProfileInfo() throws ThingsboardException { - try { - return checkNotNull(assetProfileService.findDefaultAssetProfileInfo(getTenantId())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(assetProfileService.findDefaultAssetProfileInfo(getTenantId())); } @ApiOperation(value = "Create Or Update Asset Profile (saveAssetProfile)", @@ -191,12 +179,8 @@ public class AssetProfileController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(assetProfileService.findAssetProfiles(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(assetProfileService.findAssetProfiles(getTenantId(), pageLink)); } @ApiOperation(value = "Get Asset Profile infos (getAssetProfileInfos)", @@ -217,11 +201,7 @@ public class AssetProfileController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(assetProfileService.findAssetProfileInfos(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(assetProfileService.findAssetProfileInfos(getTenantId(), pageLink)); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java index 33792a703f..387f0d014b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,15 +97,11 @@ public class AuditLogController extends BaseController { @RequestParam(required = false) Long endTime, @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { - try { - checkParameter("CustomerId", strCustomerId); - TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - List actionTypes = parseActionTypesStr(actionTypesStr); - return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + checkParameter("CustomerId", strCustomerId); + TenantId tenantId = getCurrentUser().getTenantId(); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + List actionTypes = parseActionTypesStr(actionTypesStr); + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink)); } @ApiOperation(value = "Get audit logs by user id (getAuditLogsByUserId)", @@ -135,15 +131,11 @@ public class AuditLogController extends BaseController { @RequestParam(required = false) Long endTime, @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { - try { - checkParameter("UserId", strUserId); - TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - List actionTypes = parseActionTypesStr(actionTypesStr); - return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + checkParameter("UserId", strUserId); + TenantId tenantId = getCurrentUser().getTenantId(); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + List actionTypes = parseActionTypesStr(actionTypesStr); + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink)); } @ApiOperation(value = "Get audit logs by entity id (getAuditLogsByEntityId)", @@ -176,16 +168,12 @@ public class AuditLogController extends BaseController { @RequestParam(required = false) Long endTime, @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { - try { - checkParameter("EntityId", strEntityId); - checkParameter("EntityType", strEntityType); - TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - List actionTypes = parseActionTypesStr(actionTypesStr); - return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + checkParameter("EntityId", strEntityId); + checkParameter("EntityType", strEntityType); + TenantId tenantId = getCurrentUser().getTenantId(); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + List actionTypes = parseActionTypesStr(actionTypesStr); + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink)); } @ApiOperation(value = "Get all audit logs (getAuditLogs)", @@ -212,14 +200,10 @@ public class AuditLogController extends BaseController { @RequestParam(required = false) Long endTime, @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - List actionTypes = parseActionTypesStr(actionTypesStr); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + List actionTypes = parseActionTypesStr(actionTypesStr); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink)); } private List parseActionTypesStr(String actionTypesStr) { diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 0cb3a3fc92..32b997c21f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -41,11 +42,13 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; +import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; @@ -62,6 +65,8 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; @RestController @TbCoreComponent @@ -69,6 +74,10 @@ import java.net.URISyntaxException; @Slf4j @RequiredArgsConstructor public class AuthController extends BaseController { + + @Value("${server.rest.rate_limits.reset_password_per_user:5:3600}") + private String defaultLimitsConfiguration; + private final ConcurrentMap resetPasswordRateLimits = new ConcurrentHashMap<>(); private final BCryptPasswordEncoder passwordEncoder; private final JwtTokenFactory tokenFactory; private final MailService mailService; @@ -83,12 +92,8 @@ public class AuthController extends BaseController { @RequestMapping(value = "/auth/user", method = RequestMethod.GET) public @ResponseBody User getUser() throws ThingsboardException { - try { - SecurityUser securityUser = getCurrentUser(); - return userService.findUserById(securityUser.getTenantId(), securityUser.getId()); - } catch (Exception e) { - throw handleException(e); - } + SecurityUser securityUser = getCurrentUser(); + return userService.findUserById(securityUser.getTenantId(), securityUser.getId()); } @ApiOperation(value = "Logout (logout)", @@ -108,31 +113,27 @@ public class AuthController extends BaseController { public ObjectNode changePassword( @ApiParam(value = "Change Password Request") @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException { - try { - String currentPassword = changePasswordRequest.getCurrentPassword(); - String newPassword = changePasswordRequest.getNewPassword(); - SecurityUser securityUser = getCurrentUser(); - UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, securityUser.getId()); - if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { - throw new ThingsboardException("Current password doesn't match!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - systemSecurityService.validatePassword(securityUser.getTenantId(), newPassword, userCredentials); - if (passwordEncoder.matches(newPassword, userCredentials.getPassword())) { - throw new ThingsboardException("New password should be different from existing!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - userCredentials.setPassword(passwordEncoder.encode(newPassword)); - userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials); + String currentPassword = changePasswordRequest.getCurrentPassword(); + String newPassword = changePasswordRequest.getNewPassword(); + SecurityUser securityUser = getCurrentUser(); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, securityUser.getId()); + if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { + throw new ThingsboardException("Current password doesn't match!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + systemSecurityService.validatePassword(securityUser.getTenantId(), newPassword, userCredentials); + if (passwordEncoder.matches(newPassword, userCredentials.getPassword())) { + throw new ThingsboardException("New password should be different from existing!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + userCredentials.setPassword(passwordEncoder.encode(newPassword)); + userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials); - sendEntityNotificationMsg(getTenantId(), userCredentials.getUserId(), EdgeEventActionType.CREDENTIALS_UPDATED); + sendEntityNotificationMsg(getTenantId(), userCredentials.getUserId(), EdgeEventActionType.CREDENTIALS_UPDATED); - eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); - ObjectNode response = JacksonUtil.newObjectNode(); - response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken()); - response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken()); - return response; - } catch (Exception e) { - throw handleException(e); - } + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); + ObjectNode response = JacksonUtil.newObjectNode(); + response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken()); + response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken()); + return response; } @ApiOperation(value = "Get the current User password policy (getUserPasswordPolicy)", @@ -140,13 +141,9 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/userPasswordPolicy", method = RequestMethod.GET) @ResponseBody public UserPasswordPolicy getUserPasswordPolicy() throws ThingsboardException { - try { - SecuritySettings securitySettings = - checkNotNull(systemSecurityService.getSecuritySettings(TenantId.SYS_TENANT_ID)); - return securitySettings.getPasswordPolicy(); - } catch (Exception e) { - throw handleException(e); - } + SecuritySettings securitySettings = + checkNotNull(systemSecurityService.getSecuritySettings(TenantId.SYS_TENANT_ID)); + return securitySettings.getPasswordPolicy(); } @ApiOperation(value = "Check Activate User Token (checkActivateToken)", @@ -211,7 +208,12 @@ public class AuthController extends BaseController { HttpStatus responseStatus; String resetURI = "/login/resetPassword"; UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken); + if (userCredentials != null) { + TbRateLimits tbRateLimits = getTbRateLimits(userCredentials.getUserId()); + if (!tbRateLimits.tryConsume()) { + return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); + } try { URI location = new URI(resetURI + "?resetToken=" + resetToken); headers.setLocation(location); @@ -241,34 +243,30 @@ public class AuthController extends BaseController { @RequestBody ActivateUserRequest activateRequest, @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException { - try { - String activateToken = activateRequest.getActivateToken(); - String password = activateRequest.getPassword(); - systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null); - String encodedPassword = passwordEncoder.encode(password); - UserCredentials credentials = userService.activateUserCredentials(TenantId.SYS_TENANT_ID, activateToken, encodedPassword); - User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId()); - UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); - SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); - userService.setUserCredentialsEnabled(user.getTenantId(), user.getId(), true); - String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); - String loginUrl = String.format("%s/login", baseUrl); - String email = user.getEmail(); + String activateToken = activateRequest.getActivateToken(); + String password = activateRequest.getPassword(); + systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null); + String encodedPassword = passwordEncoder.encode(password); + UserCredentials credentials = userService.activateUserCredentials(TenantId.SYS_TENANT_ID, activateToken, encodedPassword); + User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId()); + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); + SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); + userService.setUserCredentialsEnabled(user.getTenantId(), user.getId(), true); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); + String loginUrl = String.format("%s/login", baseUrl); + String email = user.getEmail(); - if (sendActivationMail) { - try { - mailService.sendAccountActivatedEmail(loginUrl, email); - } catch (Exception e) { - log.info("Unable to send account activation email [{}]", e.getMessage()); - } + if (sendActivationMail) { + try { + mailService.sendAccountActivatedEmail(loginUrl, email); + } catch (Exception e) { + log.info("Unable to send account activation email [{}]", e.getMessage()); } + } - sendEntityNotificationMsg(user.getTenantId(), user.getId(), EdgeEventActionType.CREDENTIALS_UPDATED); + sendEntityNotificationMsg(user.getTenantId(), user.getId(), EdgeEventActionType.CREDENTIALS_UPDATED); - return tokenFactory.createTokenPair(securityUser); - } catch (Exception e) { - throw handleException(e); - } + return tokenFactory.createTokenPair(securityUser); } @ApiOperation(value = "Reset password (resetPassword)", @@ -282,45 +280,42 @@ public class AuthController extends BaseController { @ApiParam(value = "Reset password request.") @RequestBody ResetPasswordRequest resetPasswordRequest, HttpServletRequest request) throws ThingsboardException { - try { - String resetToken = resetPasswordRequest.getResetToken(); - String password = resetPasswordRequest.getPassword(); - UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken); - if (userCredentials != null) { - systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials); - if (passwordEncoder.matches(password, userCredentials.getPassword())) { - throw new ThingsboardException("New password should be different from existing!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - String encodedPassword = passwordEncoder.encode(password); - userCredentials.setPassword(encodedPassword); - userCredentials.setResetToken(null); - userCredentials = userService.replaceUserCredentials(TenantId.SYS_TENANT_ID, userCredentials); - User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); - UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); - SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal); - String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); - String loginUrl = String.format("%s/login", baseUrl); - String email = user.getEmail(); - mailService.sendPasswordWasResetEmail(loginUrl, email); + String resetToken = resetPasswordRequest.getResetToken(); + String password = resetPasswordRequest.getPassword(); + UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken); + if (userCredentials != null) { + systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials); + if (passwordEncoder.matches(password, userCredentials.getPassword())) { + throw new ThingsboardException("New password should be different from existing!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + String encodedPassword = passwordEncoder.encode(password); + userCredentials.setPassword(encodedPassword); + userCredentials.setResetToken(null); + userCredentials = userService.replaceUserCredentials(TenantId.SYS_TENANT_ID, userCredentials); + User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); + SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); + String loginUrl = String.format("%s/login", baseUrl); + String email = user.getEmail(); + mailService.sendPasswordWasResetEmail(loginUrl, email); - eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); - return tokenFactory.createTokenPair(securityUser); - } else { - throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - } catch (Exception e) { - throw handleException(e); + return tokenFactory.createTokenPair(securityUser); + } else { + throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } } private void logLogoutAction(HttpServletRequest request) throws ThingsboardException { - try { - var user = getCurrentUser(); - systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(request), ActionType.LOGOUT, null); - eventPublisher.publishEvent(new UserSessionInvalidationEvent(user.getSessionId())); - } catch (Exception e) { - throw handleException(e); - } + var user = getCurrentUser(); + systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(request), ActionType.LOGOUT, null); + eventPublisher.publishEvent(new UserSessionInvalidationEvent(user.getSessionId())); + } + + private TbRateLimits getTbRateLimits(UserId userId) { + return resetPasswordRateLimits.computeIfAbsent(userId, + key -> new TbRateLimits(defaultLimitsConfiguration, true)); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java b/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java index 1a097e6817..1e7f3d66ce 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 43d049926b..34f065d500 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -43,6 +42,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; +import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -54,16 +54,19 @@ import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetProfileId; @@ -75,6 +78,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RpcId; @@ -83,6 +87,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; @@ -91,13 +96,18 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.query.EntityDataSortOrder; +import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; @@ -125,15 +135,19 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.user.UserSettingsService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; +import org.thingsboard.server.service.edge.instructions.EdgeInstallService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.user.TbUserSettingsService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -143,6 +157,7 @@ import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -153,9 +168,13 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; -import static org.thingsboard.server.controller.ControllerConstants.INCORRECT_TENANT_ID; +import static org.thingsboard.server.common.data.StringUtils.isNotEmpty; +import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; import static org.thingsboard.server.controller.UserController.YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -185,6 +204,9 @@ public abstract class BaseController { @Autowired protected UserService userService; + @Autowired + protected TbUserSettingsService userSettingsService; + @Autowired protected DeviceService deviceService; @@ -200,6 +222,9 @@ public abstract class BaseController { @Autowired protected AlarmSubscriptionService alarmService; + @Autowired + protected AlarmCommentService alarmCommentService; + @Autowired protected DeviceCredentialsService deviceCredentialsService; @@ -281,15 +306,24 @@ public abstract class BaseController { @Autowired(required = false) protected EdgeRpcService edgeRpcService; + @Autowired(required = false) + protected EdgeInstallService edgeInstallService; + @Autowired protected TbNotificationEntityService notificationEntityService; + @Autowired + protected EntityActionService entityActionService; + @Autowired protected QueueService queueService; @Autowired protected EntitiesVersionControlService vcService; + @Autowired + protected ExportableEntitiesService entitiesService; + @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; @@ -362,11 +396,17 @@ public abstract class BaseController { * */ @ExceptionHandler(MethodArgumentNotValidException.class) public void handleValidationError(MethodArgumentNotValidException e, HttpServletResponse response) { - String errorMessage = "Validation error: " + e.getBindingResult().getAllErrors().stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) + String errorMessage = "Validation error: " + e.getFieldErrors().stream() + .map(fieldError -> { + String property = fieldError.getField(); + if (property.equals("valid") || StringUtils.endsWith(property, ".valid")) { // when custom @AssertTrue is used + property = ""; + } + return (!property.isEmpty() ? (property + " ") : "") + fieldError.getDefaultMessage(); + }) .collect(Collectors.joining(", ")); ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS); - handleThingsboardException(thingsboardException, response); + handleControllerException(thingsboardException, response); } T checkNotNull(T reference) throws ThingsboardException { @@ -408,6 +448,14 @@ public abstract class BaseController { } } + protected T checkEnumParameter(String name, String param, Function valueOf) throws ThingsboardException { + try { + return valueOf.apply(param.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ThingsboardException(name + " \"" + param + "\" is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + UUID toUUID(String id) throws ThingsboardException { try { return UUID.fromString(id); @@ -452,27 +500,11 @@ public abstract class BaseController { } Tenant checkTenantId(TenantId tenantId, Operation operation) throws ThingsboardException { - try { - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Tenant tenant = tenantService.findTenantById(tenantId); - checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); - return tenant; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(tenantId, (t, i) -> tenantService.findTenantById(tenantId), operation); } TenantInfo checkTenantInfoId(TenantId tenantId, Operation operation) throws ThingsboardException { - try { - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - TenantInfo tenant = tenantService.findTenantInfoById(tenantId); - checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); - return tenant; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(tenantId, (t, i) -> tenantService.findTenantInfoById(tenantId), operation); } TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException { @@ -492,33 +524,16 @@ public abstract class BaseController { } Customer checkCustomerId(CustomerId customerId, Operation operation) throws ThingsboardException { - try { - validateId(customerId, "Incorrect customerId " + customerId); - Customer customer = customerService.findCustomerById(getTenantId(), customerId); - checkNotNull(customer, "Customer with id [" + customerId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer); - return customer; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(customerId, customerService::findCustomerById, operation); } User checkUserId(UserId userId, Operation operation) throws ThingsboardException { - try { - validateId(userId, "Incorrect userId " + userId); - User user = userService.findUserById(getCurrentUser().getTenantId(), userId); - checkNotNull(user, "User with id [" + userId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user); - return user; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(userId, userService::findUserById, operation); } protected void checkEntity(I entityId, T entity, Resource resource) throws ThingsboardException { if (entityId == null) { - accessControlService - .checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity); + accessControlService.checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity); } else { checkEntityId(entityId, Operation.WRITE); } @@ -589,203 +604,107 @@ public abstract class BaseController { checkQueueId(new QueueId(entityId.getId()), operation); return; default: - throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); + checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation); } } catch (Exception e) { throw handleException(e, false); } } - Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { + protected & HasTenantId, I extends EntityId> E checkEntityId(I entityId, ThrowingBiFunction findingFunction, Operation operation) throws ThingsboardException { try { - validateId(deviceId, "Incorrect deviceId " + deviceId); - Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId); - checkNotNull(device, "Device with id [" + deviceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); - return device; + validateId((UUIDBased) entityId, "Invalid entity id"); + SecurityUser user = getCurrentUser(); + E entity = findingFunction.apply(user.getTenantId(), entityId); + checkNotNull(entity, entityId.getEntityType().getNormalName() + " with id [" + entityId + "] is not found"); + return checkEntity(user, entity, operation); } catch (Exception e) { throw handleException(e, false); } } + protected & HasTenantId, I extends EntityId> E checkEntity(SecurityUser user, E entity, Operation operation) throws ThingsboardException { + checkNotNull(entity, "Entity not found"); + accessControlService.checkPermission(user, Resource.of(entity.getId().getEntityType()), operation, entity.getId(), entity); + return entity; + } + + Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { + return checkEntityId(deviceId, deviceService::findDeviceById, operation); + } + DeviceInfo checkDeviceInfoId(DeviceId deviceId, Operation operation) throws ThingsboardException { - try { - validateId(deviceId, "Incorrect deviceId " + deviceId); - DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId); - checkNotNull(device, "Device with id [" + deviceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); - return device; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(deviceId, deviceService::findDeviceInfoById, operation); } DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException { - try { - validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId); - checkNotNull(deviceProfile, "Device profile with id [" + deviceProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); - return deviceProfile; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(deviceProfileId, deviceProfileService::findDeviceProfileById, operation); } protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { - try { - validateId(entityViewId, "Incorrect entityViewId " + entityViewId); - EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId); - checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); - return entityView; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(entityViewId, entityViewService::findEntityViewById, operation); } EntityViewInfo checkEntityViewInfoId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { - try { - validateId(entityViewId, "Incorrect entityViewId " + entityViewId); - EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); - checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); - return entityView; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(entityViewId, entityViewService::findEntityViewInfoById, operation); } Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { - try { - validateId(assetId, "Incorrect assetId " + assetId); - Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId); - checkNotNull(asset, "Asset with id [" + assetId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); - return asset; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(assetId, assetService::findAssetById, operation); } AssetInfo checkAssetInfoId(AssetId assetId, Operation operation) throws ThingsboardException { - try { - validateId(assetId, "Incorrect assetId " + assetId); - AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); - checkNotNull(asset, "Asset with id [" + assetId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); - return asset; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(assetId, assetService::findAssetInfoById, operation); } AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation) throws ThingsboardException { - try { - validateId(assetProfileId, "Incorrect assetProfileId " + assetProfileId); - AssetProfile assetProfile = assetProfileService.findAssetProfileById(getCurrentUser().getTenantId(), assetProfileId); - checkNotNull(assetProfile, "Asset profile with id [" + assetProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile); - return assetProfile; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(assetProfileId, assetProfileService::findAssetProfileById, operation); } Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { - try { - validateId(alarmId, "Incorrect alarmId " + alarmId); - Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm); - return alarm; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(alarmId, alarmService::findAlarmById, operation); } AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException { + return checkEntityId(alarmId, alarmService::findAlarmInfoById, operation); + } + + AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) throws ThingsboardException { try { - validateId(alarmId, "Incorrect alarmId " + alarmId); - AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkNotNull(alarmInfo, "Alarm with id [" + alarmId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo); - return alarmInfo; + validateId(alarmCommentId, "Incorrect alarmCommentId " + alarmCommentId); + AlarmComment alarmComment = alarmCommentService.findAlarmCommentByIdAsync(getCurrentUser().getTenantId(), alarmCommentId).get(); + checkNotNull(alarmComment, "Alarm comment with id [" + alarmCommentId + "] is not found"); + if (!alarmId.equals(alarmComment.getAlarmId())) { + throw new ThingsboardException("Alarm id does not match with comment alarm id", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + return alarmComment; } catch (Exception e) { throw handleException(e, false); } } WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException { - try { - validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId); - checkNotNull(widgetsBundle, "Widgets bundle with id [" + widgetsBundleId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); - return widgetsBundle; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(widgetsBundleId, widgetsBundleService::findWidgetsBundleById, operation); } WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { - try { - validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); - WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId); - checkNotNull(widgetTypeDetails, "Widget type with id [" + widgetTypeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); - return widgetTypeDetails; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeDetailsById, operation); } Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { - try { - validateId(dashboardId, "Incorrect dashboardId " + dashboardId); - Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId); - checkNotNull(dashboard, "Dashboard with id [" + dashboardId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard); - return dashboard; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(dashboardId, dashboardService::findDashboardById, operation); } Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException { - try { - validateId(edgeId, "Incorrect edgeId " + edgeId); - Edge edge = edgeService.findEdgeById(getTenantId(), edgeId); - checkNotNull(edge, "Edge with id [" + edgeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); - return edge; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(edgeId, edgeService::findEdgeById, operation); } EdgeInfo checkEdgeInfoId(EdgeId edgeId, Operation operation) throws ThingsboardException { - try { - validateId(edgeId, "Incorrect edgeId " + edgeId); - EdgeInfo edge = edgeService.findEdgeInfoById(getCurrentUser().getTenantId(), edgeId); - checkNotNull(edge, "Edge with id [" + edgeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); - return edge; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(edgeId, edgeService::findEdgeInfoById, operation); } DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException { - try { - validateId(dashboardId, "Incorrect dashboardId " + dashboardId); - DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(getCurrentUser().getTenantId(), dashboardId); - checkNotNull(dashboardInfo, "Dashboard with id [" + dashboardId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboardInfo); - return dashboardInfo; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(dashboardId, dashboardService::findDashboardInfoById, operation); } ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { @@ -816,11 +735,7 @@ public abstract class BaseController { } protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException { - validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); - RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId); - checkNotNull(ruleChain, "Rule chain with id [" + ruleChainId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); - return ruleChain; + return checkEntityId(ruleChainId, ruleChainService::findRuleChainById, operation); } protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { @@ -832,70 +747,27 @@ public abstract class BaseController { } TbResource checkResourceId(TbResourceId resourceId, Operation operation) throws ThingsboardException { - try { - validateId(resourceId, "Incorrect resourceId " + resourceId); - TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId); - checkNotNull(resource, "Resource with id [" + resourceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource); - return resource; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(resourceId, resourceService::findResourceById, operation); } TbResourceInfo checkResourceInfoId(TbResourceId resourceId, Operation operation) throws ThingsboardException { - try { - validateId(resourceId, "Incorrect resourceId " + resourceId); - TbResourceInfo resourceInfo = resourceService.findResourceInfoById(getCurrentUser().getTenantId(), resourceId); - checkNotNull(resourceInfo, "Resource with id [" + resourceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resourceInfo); - return resourceInfo; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(resourceId, resourceService::findResourceInfoById, operation); } OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { - try { - validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); - OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId); - checkNotNull(otaPackage, "OTA package with id [" + otaPackageId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); - return otaPackage; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(otaPackageId, otaPackageService::findOtaPackageById, operation); } OtaPackageInfo checkOtaPackageInfoId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { - try { - validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); - OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId); - checkNotNull(otaPackageIn, "OTA package with id [" + otaPackageId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn); - return otaPackageIn; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(otaPackageId, otaPackageService::findOtaPackageInfoById, operation); } Rpc checkRpcId(RpcId rpcId, Operation operation) throws ThingsboardException { - try { - validateId(rpcId, "Incorrect rpcId " + rpcId); - Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId); - checkNotNull(rpc, "RPC with id [" + rpcId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc); - return rpc; - } catch (Exception e) { - throw handleException(e, false); - } + return checkEntityId(rpcId, rpcService::findById, operation); } protected Queue checkQueueId(QueueId queueId, Operation operation) throws ThingsboardException { - validateId(queueId, "Incorrect queueId " + queueId); - Queue queue = queueService.findQueueById(getCurrentUser().getTenantId(), queueId); - checkNotNull(queue); - accessControlService.checkPermission(getCurrentUser(), Resource.QUEUE, operation, queueId, queue); + Queue queue = checkEntityId(queueId, queueService::findQueueById, operation); TenantId tenantId = getTenantId(); if (queue.getTenantId().isNullUid() && !tenantId.isNullUid()) { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); @@ -915,6 +787,40 @@ public abstract class BaseController { return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; } + protected > void logEntityAction(SecurityUser user, EntityType entityType, E savedEntity, ActionType actionType) { + logEntityAction(user, entityType, null, savedEntity, actionType, null); + } + + protected > void logEntityAction(SecurityUser user, EntityType entityType, E entity, E savedEntity, ActionType actionType, Exception e) { + EntityId entityId = savedEntity != null ? savedEntity.getId() : emptyId(entityType); + entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity, + user.getCustomerId(), actionType, e); + } + + protected > E doSaveAndLog(EntityType entityType, E entity, BiFunction savingFunction) throws Exception { + ActionType actionType = entity.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + SecurityUser user = getCurrentUser(); + try { + E savedEntity = savingFunction.apply(user.getTenantId(), entity); + logEntityAction(user, entityType, savedEntity, actionType); + return savedEntity; + } catch (Exception e) { + logEntityAction(user, entityType, entity, null, actionType, e); + throw e; + } + } + + protected , I extends EntityId> void doDeleteAndLog(EntityType entityType, E entity, BiConsumer deleteFunction) throws Exception { + SecurityUser user = getCurrentUser(); + try { + deleteFunction.accept(user.getTenantId(), entity.getId()); + logEntityAction(user, entityType, entity, ActionType.DELETED); + } catch (Exception e) { + logEntityAction(user, entityType, entity, entity, ActionType.DELETED, e); + throw e; + } + } + protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); } @@ -959,4 +865,18 @@ public abstract class BaseController { }, MoreExecutors.directExecutor()); return deferredResult; } + + protected EntityDataSortOrder createEntityDataSortOrder(String sortProperty, String sortOrder) { + if (isNotEmpty(sortProperty)) { + EntityDataSortOrder entityDataSortOrder = new EntityDataSortOrder(); + entityDataSortOrder.setKey(new EntityKey(ENTITY_FIELD, sortProperty)); + if (isNotEmpty(sortOrder)) { + entityDataSortOrder.setDirection(EntityDataSortOrder.Direction.valueOf(sortOrder)); + } + return entityDataSortOrder; + } else { + return null; + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java index 31f64254af..33a4edfbe0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,11 +57,7 @@ public class ComponentDescriptorController extends BaseController { @ApiParam(value = "Component Descriptor class name", required = true) @PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException { checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz); - try { - return checkComponentDescriptorByClazz(strComponentDescriptorClazz); - } catch (Exception e) { - throw handleException(e); - } + return checkComponentDescriptorByClazz(strComponentDescriptorClazz); } @ApiOperation(value = "Get Component Descriptors (getComponentDescriptorsByType)", @@ -76,11 +72,7 @@ public class ComponentDescriptorController extends BaseController { @ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE") @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { checkParameter("componentType", strComponentType); - try { - return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType)); - } catch (Exception e) { - throw handleException(e); - } + return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType)); } @ApiOperation(value = "Get Component Descriptors (getComponentDescriptorsByTypes)", @@ -95,15 +87,11 @@ public class ComponentDescriptorController extends BaseController { @ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE") @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { checkArrayParameter("componentTypes", strComponentTypes); - try { - Set componentTypes = new HashSet<>(); - for (String strComponentType : strComponentTypes) { - componentTypes.add(ComponentType.valueOf(strComponentType)); - } - return checkComponentDescriptorsByTypes(componentTypes, getRuleChainType(strRuleChainType)); - } catch (Exception e) { - throw handleException(e); + Set componentTypes = new HashSet<>(); + for (String strComponentType : strComponentTypes) { + componentTypes.add(ComponentType.valueOf(strComponentType)); } + return checkComponentDescriptorsByTypes(componentTypes, getRuleChainType(strRuleChainType)); } private RuleChainType getRuleChainType(String strRuleChainType) { diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index 2409a19ac5..a060335713 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ public class ControllerConstants { protected static final String EDGE_ID = "edgeId"; protected static final String RPC_ID = "rpcId"; protected static final String ENTITY_ID = "entityId"; + protected static final String ASSIGNEE_ID = "assigneeId"; protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " + "The result is wrapped with PageData object that allows you to iterate over result set using pagination. " + "See the 'Model' tab of the Response Class for more details. "; @@ -44,6 +45,9 @@ public class ControllerConstants { protected static final String USER_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ALARM_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String ASSIGN_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + + protected static final String ALARM_COMMENT_ID_PARAM_DESCRIPTION = "A string value representing the alarm comment id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'"; @@ -100,6 +104,7 @@ public class ControllerConstants { protected static final String ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault"; protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status"; + protected static final String ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, id"; protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id"; protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root"; diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 591eaa1318..8156c72a41 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,16 +79,12 @@ public class CustomerController extends BaseController { @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId, Operation.READ); - if (!customer.getAdditionalInfo().isNull()) { - processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD); - } - return customer; - } catch (Exception e) { - throw handleException(e); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + Customer customer = checkCustomerId(customerId, Operation.READ); + if (!customer.getAdditionalInfo().isNull()) { + processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD); } + return customer; } @@ -102,17 +98,13 @@ public class CustomerController extends BaseController { @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId, Operation.READ); - ObjectMapper objectMapper = new ObjectMapper(); - ObjectNode infoObject = objectMapper.createObjectNode(); - infoObject.put("title", customer.getTitle()); - infoObject.put(IS_PUBLIC, customer.isPublic()); - return infoObject; - } catch (Exception e) { - throw handleException(e); - } + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + Customer customer = checkCustomerId(customerId, Operation.READ); + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode infoObject = objectMapper.createObjectNode(); + infoObject.put("title", customer.getTitle()); + infoObject.put(IS_PUBLIC, customer.isPublic()); + return infoObject; } @ApiOperation(value = "Get Customer Title (getCustomerTitleById)", @@ -125,13 +117,9 @@ public class CustomerController extends BaseController { @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId, Operation.READ); - return customer.getTitle(); - } catch (Exception e) { - throw handleException(e); - } + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + Customer customer = checkCustomerId(customerId, Operation.READ); + return customer.getTitle(); } @ApiOperation(value = "Create or update Customer (saveCustomer)", @@ -182,13 +170,9 @@ public class CustomerController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink)); } @ApiOperation(value = "Get Tenant Customer by Customer title (getTenantCustomer)", @@ -199,11 +183,7 @@ public class CustomerController extends BaseController { public Customer getTenantCustomer( @ApiParam(value = "A string value representing the Customer title.") @RequestParam String customerTitle) throws ThingsboardException { - try { TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(customerService.findCustomerByTenantIdAndTitle(tenantId, customerTitle), "Customer with title [" + customerTitle + "] is not found"); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(customerService.findCustomerByTenantIdAndTitle(tenantId, customerTitle), "Customer with title [" + customerTitle + "] is not found"); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 9ceeb0d14c..d4e5169e7e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,12 +140,8 @@ public class DashboardController extends BaseController { @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); - try { - DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - return checkDashboardInfoId(dashboardId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + return checkDashboardInfoId(dashboardId, Operation.READ); } @ApiOperation(value = "Get Dashboard (getDashboardById)", @@ -159,12 +155,8 @@ public class DashboardController extends BaseController { @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); - try { - DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - return checkDashboardId(dashboardId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + return checkDashboardId(dashboardId, Operation.READ); } @ApiOperation(value = "Create Or Update Dashboard (saveDashboard)", @@ -362,14 +354,10 @@ public class DashboardController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); - checkTenantId(tenantId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); + checkTenantId(tenantId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); } @ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)", @@ -392,16 +380,12 @@ public class DashboardController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (mobile != null && mobile) { - return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink)); - } else { - return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (mobile != null && mobile) { + return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink)); + } else { + return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); } } @@ -428,18 +412,14 @@ public class DashboardController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (mobile != null && mobile) { - return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } else { - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (mobile != null && mobile) { + return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); + } else { + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -453,31 +433,27 @@ public class DashboardController extends BaseController { @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) @ResponseBody public HomeDashboard getHomeDashboard() throws ThingsboardException { - try { - SecurityUser securityUser = getCurrentUser(); - if (securityUser.isSystemAdmin()) { - return null; + SecurityUser securityUser = getCurrentUser(); + if (securityUser.isSystemAdmin()) { + return null; + } + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); + JsonNode additionalInfo = user.getAdditionalInfo(); + HomeDashboard homeDashboard; + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + if (homeDashboard == null) { + if (securityUser.isCustomerUser()) { + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); + additionalInfo = customer.getAdditionalInfo(); + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); } - User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); - JsonNode additionalInfo = user.getAdditionalInfo(); - HomeDashboard homeDashboard; - homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); if (homeDashboard == null) { - if (securityUser.isCustomerUser()) { - Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); - additionalInfo = customer.getAdditionalInfo(); - homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); - } - if (homeDashboard == null) { - Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); - additionalInfo = tenant.getAdditionalInfo(); - homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); - } + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); + additionalInfo = tenant.getAdditionalInfo(); + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); } - return homeDashboard; - } catch (Exception e) { - throw handleException(e); } + return homeDashboard; } @ApiOperation(value = "Get Home Dashboard Info (getHomeDashboardInfo)", @@ -490,31 +466,27 @@ public class DashboardController extends BaseController { @RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET) @ResponseBody public HomeDashboardInfo getHomeDashboardInfo() throws ThingsboardException { - try { - SecurityUser securityUser = getCurrentUser(); - if (securityUser.isSystemAdmin()) { - return null; + SecurityUser securityUser = getCurrentUser(); + if (securityUser.isSystemAdmin()) { + return null; + } + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); + JsonNode additionalInfo = user.getAdditionalInfo(); + HomeDashboardInfo homeDashboardInfo; + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); + if (homeDashboardInfo == null) { + if (securityUser.isCustomerUser()) { + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); + additionalInfo = customer.getAdditionalInfo(); + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); } - User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); - JsonNode additionalInfo = user.getAdditionalInfo(); - HomeDashboardInfo homeDashboardInfo; - homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); if (homeDashboardInfo == null) { - if (securityUser.isCustomerUser()) { - Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); - additionalInfo = customer.getAdditionalInfo(); - homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); - } - if (homeDashboardInfo == null) { - Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); - additionalInfo = tenant.getAdditionalInfo(); - homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); - } + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); + additionalInfo = tenant.getAdditionalInfo(); + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); } - return homeDashboardInfo; - } catch (Exception e) { - throw handleException(e); } + return homeDashboardInfo; } @ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", @@ -525,22 +497,18 @@ public class DashboardController extends BaseController { @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) @ResponseBody public HomeDashboardInfo getTenantHomeDashboardInfo() throws ThingsboardException { - try { - Tenant tenant = tenantService.findTenantById(getTenantId()); - JsonNode additionalInfo = tenant.getAdditionalInfo(); - DashboardId dashboardId = null; - boolean hideDashboardToolbar = true; - if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { - String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); - dashboardId = new DashboardId(toUUID(strDashboardId)); - if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { - hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); - } + Tenant tenant = tenantService.findTenantById(getTenantId()); + JsonNode additionalInfo = tenant.getAdditionalInfo(); + DashboardId dashboardId = null; + boolean hideDashboardToolbar = true; + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + dashboardId = new DashboardId(toUUID(strDashboardId)); + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); } - return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); - } catch (Exception e) { - throw handleException(e); } + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); } @ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", @@ -554,27 +522,23 @@ public class DashboardController extends BaseController { @ApiParam(value = "A JSON object that represents home dashboard id and other parameters", required = true) @RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { - try { - if (homeDashboardInfo.getDashboardId() != null) { - checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); - } - Tenant tenant = tenantService.findTenantById(getTenantId()); - JsonNode additionalInfo = tenant.getAdditionalInfo(); - if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) { - additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - } - if (homeDashboardInfo.getDashboardId() != null) { - ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString()); - ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_HIDE_TOOLBAR, homeDashboardInfo.isHideDashboardToolbar()); - } else { - ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_ID); - ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_HIDE_TOOLBAR); - } - tenant.setAdditionalInfo(additionalInfo); - tenantService.saveTenant(tenant); - } catch (Exception e) { - throw handleException(e); + if (homeDashboardInfo.getDashboardId() != null) { + checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); + } + Tenant tenant = tenantService.findTenantById(getTenantId()); + JsonNode additionalInfo = tenant.getAdditionalInfo(); + if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) { + additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + } + if (homeDashboardInfo.getDashboardId() != null) { + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString()); + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_HIDE_TOOLBAR, homeDashboardInfo.isHideDashboardToolbar()); + } else { + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_ID); + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_HIDE_TOOLBAR); } + tenant.setAdditionalInfo(additionalInfo); + tenantService.saveTenant(tenant); } private HomeDashboardInfo extractHomeDashboardInfoFromAdditionalInfo(JsonNode additionalInfo) { @@ -681,28 +645,24 @@ public class DashboardController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("edgeId", strEdgeId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - PageData nonFilteredResult = dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); - List filteredDashboards = nonFilteredResult.getData().stream().filter(dashboardInfo -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, Operation.READ, dashboardInfo.getId(), dashboardInfo); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - PageData filteredResult = new PageData<>(filteredDashboards, - nonFilteredResult.getTotalPages(), - nonFilteredResult.getTotalElements(), - nonFilteredResult.hasNext()); - return checkNotNull(filteredResult); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + PageData nonFilteredResult = dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); + List filteredDashboards = nonFilteredResult.getData().stream().filter(dashboardInfo -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, Operation.READ, dashboardInfo.getId(), dashboardInfo); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + PageData filteredResult = new PageData<>(filteredDashboards, + nonFilteredResult.getTotalPages(), + nonFilteredResult.getTotalElements(), + nonFilteredResult.hasNext()); + return checkNotNull(filteredResult); } private Set customerIdFromStr(String[] strCustomerIds) { diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index ae1558381f..0e6e19ad8d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,6 +77,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_AUTHORITY_PARAGRAPH; @@ -308,16 +309,12 @@ public class DeviceController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink)); - } else { - return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink)); } } @@ -343,19 +340,15 @@ public class DeviceController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder ) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink)); - } else if (deviceProfileId != null && deviceProfileId.length() > 0) { - DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId)); - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink)); - } else { - return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink)); + } else if (deviceProfileId != null && deviceProfileId.length() > 0) { + DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId)); + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink)); + } else { + return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink)); } } @@ -368,12 +361,8 @@ public class DeviceController extends BaseController { public Device getTenantDevice( @ApiParam(value = DEVICE_NAME_DESCRIPTION) @RequestParam String deviceName) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); } @ApiOperation(value = "Get Customer Devices (getCustomerDevices)", @@ -398,18 +387,14 @@ public class DeviceController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); - } else { - return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else { + return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -437,21 +422,17 @@ public class DeviceController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); - } else if (deviceProfileId != null && deviceProfileId.length() > 0) { - DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId)); - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink)); - } else { - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else if (deviceProfileId != null && deviceProfileId.length() > 0) { + DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId)); + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink)); + } else { + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -462,26 +443,22 @@ public class DeviceController extends BaseController { @ResponseBody public List getDevicesByIds( @ApiParam(value = "A list of devices ids, separated by comma ','") - @RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException { + @RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException, ExecutionException, InterruptedException { checkArrayParameter("deviceIds", strDeviceIds); - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - CustomerId customerId = user.getCustomerId(); - List deviceIds = new ArrayList<>(); - for (String strDeviceId : strDeviceIds) { - deviceIds.add(new DeviceId(toUUID(strDeviceId))); - } - ListenableFuture> devices; - if (customerId == null || customerId.isNullUid()) { - devices = deviceService.findDevicesByTenantIdAndIdsAsync(tenantId, deviceIds); - } else { - devices = deviceService.findDevicesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, deviceIds); - } - return checkNotNull(devices.get()); - } catch (Exception e) { - throw handleException(e); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + CustomerId customerId = user.getCustomerId(); + List deviceIds = new ArrayList<>(); + for (String strDeviceId : strDeviceIds) { + deviceIds.add(new DeviceId(toUUID(strDeviceId))); + } + ListenableFuture> devices; + if (customerId == null || customerId.isNullUid()) { + devices = deviceService.findDevicesByTenantIdAndIdsAsync(tenantId, deviceIds); + } else { + devices = deviceService.findDevicesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, deviceIds); } + return checkNotNull(devices.get()); } @ApiOperation(value = "Find related devices (findByQuery)", @@ -493,25 +470,21 @@ public class DeviceController extends BaseController { @ResponseBody public List findByQuery( @ApiParam(value = "The device search query JSON") - @RequestBody DeviceSearchQuery query) throws ThingsboardException { + @RequestBody DeviceSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getDeviceTypes()); checkEntityId(query.getParameters().getEntityId(), Operation.READ); - try { - List devices = checkNotNull(deviceService.findDevicesByQuery(getCurrentUser().getTenantId(), query).get()); - devices = devices.stream().filter(device -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - return devices; - } catch (Exception e) { - throw handleException(e); - } + List devices = checkNotNull(deviceService.findDevicesByQuery(getCurrentUser().getTenantId(), query).get()); + devices = devices.stream().filter(device -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + return devices; } @ApiOperation(value = "Get Device Types (getDeviceTypes)", @@ -520,15 +493,11 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/types", method = RequestMethod.GET) @ResponseBody - public List getDeviceTypes() throws ThingsboardException { - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - ListenableFuture> deviceTypes = deviceService.findDeviceTypesByTenantId(tenantId); - return checkNotNull(deviceTypes.get()); - } catch (Exception e) { - throw handleException(e); - } + public List getDeviceTypes() throws ThingsboardException, ExecutionException, InterruptedException { + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + ListenableFuture> deviceTypes = deviceService.findDeviceTypesByTenantId(tenantId); + return checkNotNull(deviceTypes.get()); } @ApiOperation(value = "Claim device (claimDevice)", @@ -722,33 +691,29 @@ public class DeviceController extends BaseController { @ApiParam(value = "Timestamp. Devices with creation time after it won't be queried") @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.READ); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - PageData nonFilteredResult; - if (type != null && type.trim().length() > 0) { - nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); - } else { - nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink); - } - List filteredDevices = nonFilteredResult.getData().stream().filter(device -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - PageData filteredResult = new PageData<>(filteredDevices, - nonFilteredResult.getTotalPages(), - nonFilteredResult.getTotalElements(), - nonFilteredResult.hasNext()); - return checkNotNull(filteredResult); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + PageData nonFilteredResult; + if (type != null && type.trim().length() > 0) { + nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); + } else { + nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink); } + List filteredDevices = nonFilteredResult.getData().stream().filter(device -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + PageData filteredResult = new PageData<>(filteredDevices, + nonFilteredResult.getTotalPages(), + nonFilteredResult.getTotalElements(), + nonFilteredResult.hasNext()); + return checkNotNull(filteredResult); } @ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)", @@ -766,14 +731,10 @@ public class DeviceController extends BaseController { @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { checkParameter("OtaPackageType", otaPackageType); checkParameter("DeviceProfileId", deviceProfileId); - try { - return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( - getTenantId(), - new DeviceProfileId(UUID.fromString(deviceProfileId)), - OtaPackageType.valueOf(otaPackageType)); - } catch (Exception e) { - throw handleException(e); - } + return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( + getTenantId(), + new DeviceProfileId(UUID.fromString(deviceProfileId)), + OtaPackageType.valueOf(otaPackageType)); } @ApiOperation(value = "Import the bulk of devices (processDevicesBulkImport)", diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index bd2a5a2ad9..c888d67252 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,12 +87,8 @@ public class DeviceProfileController extends BaseController { @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); - try { - DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); - return checkDeviceProfileId(deviceProfileId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); + return checkDeviceProfileId(deviceProfileId, Operation.READ); } @ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)", @@ -106,12 +102,8 @@ public class DeviceProfileController extends BaseController { @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); - try { - DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); - return new DeviceProfileInfo(checkDeviceProfileId(deviceProfileId, Operation.READ)); - } catch (Exception e) { - throw handleException(e); - } + DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); + return new DeviceProfileInfo(checkDeviceProfileId(deviceProfileId, Operation.READ)); } @ApiOperation(value = "Get Default Device Profile (getDefaultDeviceProfileInfo)", @@ -122,11 +114,7 @@ public class DeviceProfileController extends BaseController { @RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET) @ResponseBody public DeviceProfileInfo getDefaultDeviceProfileInfo() throws ThingsboardException { - try { - return checkNotNull(deviceProfileService.findDefaultDeviceProfileInfo(getTenantId())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(deviceProfileService.findDefaultDeviceProfileInfo(getTenantId())); } @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", @@ -150,11 +138,7 @@ public class DeviceProfileController extends BaseController { deviceProfileId = null; } - try { - return timeseriesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId); - } catch (Exception e) { - throw handleException(e); - } + return timeseriesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId); } @ApiOperation(value = "Get attribute keys (getAttributesKeys)", @@ -178,11 +162,7 @@ public class DeviceProfileController extends BaseController { deviceProfileId = null; } - try { - return attributesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId); - } catch (Exception e) { - throw handleException(e); - } + return attributesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId); } @ApiOperation(value = "Create Or Update Device Profile (saveDeviceProfile)", @@ -256,12 +236,8 @@ public class DeviceProfileController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink)); } @ApiOperation(value = "Get Device Profiles for transport type (getDeviceProfileInfos)", @@ -284,11 +260,7 @@ public class DeviceProfileController extends BaseController { @RequestParam(required = false) String sortOrder, @ApiParam(value = "Type of the transport", allowableValues = TRANSPORT_TYPE_ALLOWABLE_VALUES) @RequestParam(required = false) String transportType) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 6a5dd44d0b..5ef1c1b572 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInfo; +import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -63,9 +64,11 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; @@ -116,12 +119,8 @@ public class EdgeController extends BaseController { public Edge getEdgeById(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - return checkEdgeId(edgeId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + return checkEdgeId(edgeId, Operation.READ); } @ApiOperation(value = "Get Edge Info (getEdgeInfoById)", @@ -133,12 +132,8 @@ public class EdgeController extends BaseController { public EdgeInfo getEdgeInfoById(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - return checkEdgeInfoId(edgeId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + return checkEdgeInfoId(edgeId, Operation.READ); } @ApiOperation(value = "Create Or Update Edge (saveEdge)", @@ -203,13 +198,9 @@ public class EdgeController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink)); } @ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)", @@ -285,16 +276,12 @@ public class EdgeController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(edgeService.findEdgesByTenantIdAndType(tenantId, type, pageLink)); - } else { - return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(edgeService.findEdgesByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink)); } } @@ -318,16 +305,12 @@ public class EdgeController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(edgeService.findEdgeInfosByTenantIdAndType(tenantId, type, pageLink)); - } else { - return checkNotNull(edgeService.findEdgeInfosByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(edgeService.findEdgeInfosByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(edgeService.findEdgeInfosByTenantId(tenantId, pageLink)); } } @@ -340,12 +323,8 @@ public class EdgeController extends BaseController { @ResponseBody public Edge getTenantEdge(@ApiParam(value = "Unique name of the edge", required = true) @RequestParam String edgeName) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName)); } @ApiOperation(value = "Set root rule chain for provided edge (setEdgeRootRuleChain)", @@ -391,22 +370,18 @@ public class EdgeController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - PageData result; - if (type != null && type.trim().length() > 0) { - result = edgeService.findEdgesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink); - } else { - result = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink); - } - return checkNotNull(result); - } catch (Exception e) { - throw handleException(e); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + PageData result; + if (type != null && type.trim().length() > 0) { + result = edgeService.findEdgesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink); + } else { + result = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink); } + return checkNotNull(result); } @ApiOperation(value = "Get Customer Edge Infos (getCustomerEdgeInfos)", @@ -431,22 +406,18 @@ public class EdgeController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - PageData result; - if (type != null && type.trim().length() > 0) { - result = edgeService.findEdgeInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink); - } else { - result = edgeService.findEdgeInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); - } - return checkNotNull(result); - } catch (Exception e) { - throw handleException(e); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + PageData result; + if (type != null && type.trim().length() > 0) { + result = edgeService.findEdgeInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink); + } else { + result = edgeService.findEdgeInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); } + return checkNotNull(result); } @ApiOperation(value = "Get Edges By Ids (getEdgesByIds)", @@ -457,27 +428,23 @@ public class EdgeController extends BaseController { @ResponseBody public List getEdgesByIds( @ApiParam(value = "A list of edges ids, separated by comma ','", required = true) - @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException { + @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException, ExecutionException, InterruptedException { checkArrayParameter("edgeIds", strEdgeIds); - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - CustomerId customerId = user.getCustomerId(); - List edgeIds = new ArrayList<>(); - for (String strEdgeId : strEdgeIds) { - edgeIds.add(new EdgeId(toUUID(strEdgeId))); - } - ListenableFuture> edgesFuture; - if (customerId == null || customerId.isNullUid()) { - edgesFuture = edgeService.findEdgesByTenantIdAndIdsAsync(tenantId, edgeIds); - } else { - edgesFuture = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds); - } - List edges = edgesFuture.get(); - return checkNotNull(edges); - } catch (Exception e) { - throw handleException(e); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + CustomerId customerId = user.getCustomerId(); + List edgeIds = new ArrayList<>(); + for (String strEdgeId : strEdgeIds) { + edgeIds.add(new EdgeId(toUUID(strEdgeId))); } + ListenableFuture> edgesFuture; + if (customerId == null || customerId.isNullUid()) { + edgesFuture = edgeService.findEdgesByTenantIdAndIdsAsync(tenantId, edgeIds); + } else { + edgesFuture = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds); + } + List edges = edgesFuture.get(); + return checkNotNull(edges); } @ApiOperation(value = "Find related edges (findByQuery)", @@ -488,27 +455,23 @@ public class EdgeController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edges", method = RequestMethod.POST) @ResponseBody - public List findByQuery(@RequestBody EdgeSearchQuery query) throws ThingsboardException { + public List findByQuery(@RequestBody EdgeSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getEdgeTypes()); checkEntityId(query.getParameters().getEntityId(), Operation.READ); - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - List edges = checkNotNull(edgeService.findEdgesByQuery(tenantId, query).get()); - edges = edges.stream().filter(edge -> { - try { - accessControlService.checkPermission(user, Resource.EDGE, Operation.READ, edge.getId(), edge); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - return edges; - } catch (Exception e) { - throw handleException(e); - } + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + List edges = checkNotNull(edgeService.findEdgesByQuery(tenantId, query).get()); + edges = edges.stream().filter(edge -> { + try { + accessControlService.checkPermission(user, Resource.EDGE, Operation.READ, edge.getId(), edge); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + return edges; } @ApiOperation(value = "Get Edge Types (getEdgeTypes)", @@ -518,15 +481,11 @@ public class EdgeController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/types", method = RequestMethod.GET) @ResponseBody - public List getEdgeTypes() throws ThingsboardException { - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - ListenableFuture> edgeTypes = edgeService.findEdgeTypesByTenantId(tenantId); - return checkNotNull(edgeTypes.get()); - } catch (Exception e) { - throw handleException(e); - } + public List getEdgeTypes() throws ThingsboardException, ExecutionException, InterruptedException { + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + ListenableFuture> edgeTypes = edgeService.findEdgeTypesByTenantId(tenantId); + return checkNotNull(edgeTypes.get()); } @ApiOperation(value = "Sync edge (syncEdge)", @@ -537,22 +496,18 @@ public class EdgeController extends BaseController { public DeferredResult syncEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { checkParameter("edgeId", strEdgeId); - try { - final DeferredResult response = new DeferredResult<>(); - if (isEdgesEnabled()) { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - edgeId = checkNotNull(edgeId); - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - ToEdgeSyncRequest request = new ToEdgeSyncRequest(UUID.randomUUID(), tenantId, edgeId); - edgeRpcService.processSyncRequest(request, fromEdgeSyncResponse -> reply(response, fromEdgeSyncResponse)); - } else { - throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); - } - return response; - } catch (Exception e) { - throw handleException(e); + final DeferredResult response = new DeferredResult<>(); + if (isEdgesEnabled()) { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + edgeId = checkNotNull(edgeId); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + ToEdgeSyncRequest request = new ToEdgeSyncRequest(UUID.randomUUID(), tenantId, edgeId); + edgeRpcService.processSyncRequest(request, fromEdgeSyncResponse -> reply(response, fromEdgeSyncResponse)); + } else { + throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); } + return response; } private void reply(DeferredResult response, FromEdgeSyncResponse fromEdgeSyncResponse) { @@ -570,15 +525,11 @@ public class EdgeController extends BaseController { @ResponseBody public String findMissingToRelatedRuleChains(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - edgeId = checkNotNull(edgeId); - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - return edgeService.findMissingToRelatedRuleChains(tenantId, edgeId, TbRuleChainInputNode.class.getName()); - } catch (Exception e) { - throw handleException(e); - } + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + edgeId = checkNotNull(edgeId); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + return edgeService.findMissingToRelatedRuleChains(tenantId, edgeId, TbRuleChainInputNode.class.getName()); } @ApiOperation(value = "Import the bulk of edges (processEdgesBulkImport)", @@ -595,4 +546,20 @@ public class EdgeController extends BaseController { return edgeBulkImportService.processBulkImport(request, user); } + + @ApiOperation(value = "Get Edge Docker Install Instructions (getEdgeDockerInstallInstructions)", + notes = "Get a docker install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH, + produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/instructions/{edgeId}", method = RequestMethod.GET) + @ResponseBody + public EdgeInstallInstructions getEdgeDockerInstallInstructions( + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @PathVariable("edgeId") String strEdgeId, + HttpServletRequest request) throws ThingsboardException { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + edgeId = checkNotNull(edgeId); + Edge edge = checkEdgeId(edgeId, Operation.READ); + return checkNotNull(edgeInstallService.getDockerInstallInstructions(getTenantId(), edge, request)); + } } diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java index 5dd43736a8..fc85f439e0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,14 +81,10 @@ public class EdgeEventController extends BaseController { @ApiParam(value = "Timestamp. Edge events with creation time after it won't be queried") @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.READ); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false)); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java index f2bfbc9478..86d0e2c344 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java index bc6c628bff..fbb8901ca9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,9 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.query.AlarmCountQuery; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.EntityCountQuery; @@ -38,6 +40,7 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.query.EntityQueryService; +import org.thingsboard.server.service.security.permission.Operation; import static org.thingsboard.server.controller.ControllerConstants.ALARM_DATA_QUERY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_COUNT_QUERY_DESCRIPTION; @@ -54,18 +57,14 @@ public class EntityQueryController extends BaseController { private static final int MAX_PAGE_SIZE = 100; @ApiOperation(value = "Count Entities by Query", notes = ENTITY_COUNT_QUERY_DESCRIPTION) - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entitiesQuery/count", method = RequestMethod.POST) @ResponseBody public long countEntitiesByQuery( @ApiParam(value = "A JSON value representing the entity count query. See API call notes above for more details.") @RequestBody EntityCountQuery query) throws ThingsboardException { checkNotNull(query); - try { - return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); - } catch (Exception e) { - throw handleException(e); - } + return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); } @ApiOperation(value = "Find Entity Data by Query", notes = ENTITY_DATA_QUERY_DESCRIPTION) @@ -76,11 +75,7 @@ public class EntityQueryController extends BaseController { @ApiParam(value = "A JSON value representing the entity data query. See API call notes above for more details.") @RequestBody EntityDataQuery query) throws ThingsboardException { checkNotNull(query); - try { - return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); - } catch (Exception e) { - throw handleException(e); - } + return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); } @ApiOperation(value = "Find Alarms by Query", notes = ALARM_DATA_QUERY_DESCRIPTION) @@ -91,11 +86,26 @@ public class EntityQueryController extends BaseController { @ApiParam(value = "A JSON value representing the alarm data query. See API call notes above for more details.") @RequestBody AlarmDataQuery query) throws ThingsboardException { checkNotNull(query); - try { - return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); - } catch (Exception e) { - throw handleException(e); + checkNotNull(query.getPageLink()); + UserId assigneeId = query.getPageLink().getAssigneeId(); + if (assigneeId != null) { + checkUserId(assigneeId, Operation.READ); + } + return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); + } + + @ApiOperation(value = "Count Alarms by Query (countAlarmsByQuery)", notes = "Returns the number of alarms that match the query definition.") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarmsQuery/count", method = RequestMethod.POST) + @ResponseBody + public long countAlarmsByQuery(@ApiParam(value = "A JSON value representing the alarm count query.") + @RequestBody AlarmCountQuery query) throws ThingsboardException { + checkNotNull(query); + UserId assigneeId = query.getAssigneeId(); + if (assigneeId != null) { + checkUserId(assigneeId, Operation.READ); } + return this.entityQueryService.countAlarmsByQuery(getCurrentUser(), query); } @ApiOperation(value = "Find Entity Keys by Query", @@ -112,15 +122,11 @@ public class EntityQueryController extends BaseController { @RequestParam("attributes") boolean isAttributes) throws ThingsboardException { TenantId tenantId = getTenantId(); checkNotNull(query); - try { - EntityDataPageLink pageLink = query.getPageLink(); - if (pageLink.getPageSize() > MAX_PAGE_SIZE) { - pageLink.setPageSize(MAX_PAGE_SIZE); - } - return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes); - } catch (Exception e) { - throw handleException(e); + EntityDataPageLink pageLink = query.getPageLink(); + if (pageLink.getPageSize() > MAX_PAGE_SIZE) { + pageLink.setPageSize(MAX_PAGE_SIZE); } + return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index efc4237b3f..08448f1d28 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; @@ -143,21 +144,17 @@ public class EntityRelationController extends BaseController { @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType) throws ThingsboardException { - try { - checkParameter(FROM_ID, strFromId); - checkParameter(FROM_TYPE, strFromType); - checkParameter(RELATION_TYPE, strRelationType); - checkParameter(TO_ID, strToId); - checkParameter(TO_TYPE, strToType); - EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); - EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(fromId, Operation.READ); - checkEntityId(toId, Operation.READ); - RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - return checkNotNull(relationService.getRelation(getTenantId(), fromId, toId, strRelationType, typeGroup)); - } catch (Exception e) { - throw handleException(e); - } + checkParameter(FROM_ID, strFromId); + checkParameter(FROM_TYPE, strFromType); + checkParameter(RELATION_TYPE, strRelationType); + checkParameter(TO_ID, strToId); + checkParameter(TO_TYPE, strToType); + EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); + EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); + checkEntityId(fromId, Operation.READ); + checkEntityId(toId, Operation.READ); + RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); + return checkNotNull(relationService.getRelation(getTenantId(), fromId, toId, strRelationType, typeGroup)); } @ApiOperation(value = "Get List of Relations (findByFrom)", @@ -176,11 +173,7 @@ public class EntityRelationController extends BaseController { EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findByFrom(getTenantId(), entityId, typeGroup))); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findByFrom(getTenantId(), entityId, typeGroup))); } @ApiOperation(value = "Get List of Relation Infos (findInfoByFrom)", @@ -193,17 +186,13 @@ public class EntityRelationController extends BaseController { public List findInfoByFrom(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) - @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { + @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findInfoByFrom(getTenantId(), entityId, typeGroup).get())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findInfoByFrom(getTenantId(), entityId, typeGroup).get())); } @ApiOperation(value = "Get List of Relations (findByFrom)", @@ -224,11 +213,7 @@ public class EntityRelationController extends BaseController { EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findByFromAndType(getTenantId(), entityId, strRelationType, typeGroup))); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findByFromAndType(getTenantId(), entityId, strRelationType, typeGroup))); } @ApiOperation(value = "Get List of Relations (findByTo)", @@ -247,11 +232,7 @@ public class EntityRelationController extends BaseController { EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findByTo(getTenantId(), entityId, typeGroup))); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findByTo(getTenantId(), entityId, typeGroup))); } @ApiOperation(value = "Get List of Relation Infos (findInfoByTo)", @@ -264,17 +245,13 @@ public class EntityRelationController extends BaseController { public List findInfoByTo(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) - @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { + @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter(TO_ID, strToId); checkParameter(TO_TYPE, strToType); EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findInfoByTo(getTenantId(), entityId, typeGroup).get())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findInfoByTo(getTenantId(), entityId, typeGroup).get())); } @ApiOperation(value = "Get List of Relations (findByTo)", @@ -295,11 +272,7 @@ public class EntityRelationController extends BaseController { EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findByToAndType(getTenantId(), entityId, strRelationType, typeGroup))); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findByToAndType(getTenantId(), entityId, strRelationType, typeGroup))); } @ApiOperation(value = "Find related entities (findByQuery)", @@ -310,16 +283,12 @@ public class EntityRelationController extends BaseController { @RequestMapping(value = "/relations", method = RequestMethod.POST) @ResponseBody public List findByQuery(@ApiParam(value = "A JSON value representing the entity relations query object.", required = true) - @RequestBody EntityRelationsQuery query) throws ThingsboardException { + @RequestBody EntityRelationsQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getFilters()); checkEntityId(query.getParameters().getEntityId(), Operation.READ); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findByQuery(getTenantId(), query).get())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findByQuery(getTenantId(), query).get())); } @ApiOperation(value = "Find related entity infos (findInfoByQuery)", @@ -330,16 +299,12 @@ public class EntityRelationController extends BaseController { @RequestMapping(value = "/relations/info", method = RequestMethod.POST) @ResponseBody public List findInfoByQuery(@ApiParam(value = "A JSON value representing the entity relations query object.", required = true) - @RequestBody EntityRelationsQuery query) throws ThingsboardException { + @RequestBody EntityRelationsQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getFilters()); checkEntityId(query.getParameters().getEntityId(), Operation.READ); - try { - return checkNotNull(filterRelationsByReadPermission(relationService.findInfoByQuery(getTenantId(), query).get())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(filterRelationsByReadPermission(relationService.findInfoByQuery(getTenantId(), query).get())); } private void checkCanCreateRelation(EntityId entityId) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index 0f299fefae..b2904a0989 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,7 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; @@ -105,11 +106,7 @@ public class EntityViewController extends BaseController { @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); - try { - return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)), Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)), Operation.READ); } @ApiOperation(value = "Get Entity View info (getEntityViewInfoById)", @@ -123,12 +120,8 @@ public class EntityViewController extends BaseController { @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); - try { - EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); - return checkEntityViewInfoId(entityViewId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); + return checkEntityViewInfoId(entityViewId, Operation.READ); } @ApiOperation(value = "Save or update entity view (saveEntityView)", @@ -177,12 +170,8 @@ public class EntityViewController extends BaseController { public EntityView getTenantEntityView( @ApiParam(value = "Entity View name") @RequestParam String entityViewName) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName)); } @ApiOperation(value = "Assign Entity View to customer (assignEntityViewToCustomer)", @@ -249,18 +238,14 @@ public class EntityViewController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type)); - } else { - return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type)); + } else { + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -286,18 +271,14 @@ public class EntityViewController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); - } else { - return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else { + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } } @@ -320,17 +301,13 @@ public class EntityViewController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - - if (type != null && type.trim().length() > 0) { - return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type)); - } else { - return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + + if (type != null && type.trim().length() > 0) { + return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type)); + } else { + return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink)); } } @@ -353,16 +330,12 @@ public class EntityViewController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (type != null && type.trim().length() > 0) { - return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndType(tenantId, type, pageLink)); - } else { - return checkNotNull(entityViewService.findEntityViewInfosByTenantId(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(entityViewService.findEntityViewInfosByTenantId(tenantId, pageLink)); } } @@ -375,25 +348,21 @@ public class EntityViewController extends BaseController { @ResponseBody public List findByQuery( @ApiParam(value = "The entity view search query JSON") - @RequestBody EntityViewSearchQuery query) throws ThingsboardException { + @RequestBody EntityViewSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getEntityViewTypes()); checkEntityId(query.getParameters().getEntityId(), Operation.READ); - try { - List entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(getTenantId(), query).get()); - entityViews = entityViews.stream().filter(entityView -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.READ, entityView.getId(), entityView); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - return entityViews; - } catch (Exception e) { - throw handleException(e); - } + List entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(getTenantId(), query).get()); + entityViews = entityViews.stream().filter(entityView -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.READ, entityView.getId(), entityView); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + return entityViews; } @ApiOperation(value = "Get Entity View Types (getEntityViewTypes)", @@ -402,15 +371,11 @@ public class EntityViewController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView/types", method = RequestMethod.GET) @ResponseBody - public List getEntityViewTypes() throws ThingsboardException { - try { - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - ListenableFuture> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId); - return checkNotNull(entityViewTypes.get()); - } catch (Exception e) { - throw handleException(e); - } + public List getEntityViewTypes() throws ThingsboardException, ExecutionException, InterruptedException { + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + ListenableFuture> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId); + return checkNotNull(entityViewTypes.get()); } @ApiOperation(value = "Make entity view publicly available (assignEntityViewToPublicCustomer)", @@ -426,11 +391,7 @@ public class EntityViewController extends BaseController { checkParameter(ENTITY_VIEW_ID, strEntityViewId); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); checkEntityViewId(entityViewId, Operation.ASSIGN_TO_CUSTOMER); - - Customer publicCustomer = customerService.findOrCreatePublicCustomer(getTenantId()); - - return tbEntityViewService.assignEntityViewToPublicCustomer(getTenantId(), getCurrentUser().getCustomerId(), - publicCustomer, entityViewId, getCurrentUser()); + return tbEntityViewService.assignEntityViewToPublicCustomer(getTenantId(), entityViewId, getCurrentUser()); } @ApiOperation(value = "Assign entity view to edge (assignEntityViewToEdge)", @@ -497,32 +458,28 @@ public class EntityViewController extends BaseController { @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.READ); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - PageData nonFilteredResult; - if (type != null && type.trim().length() > 0) { - nonFilteredResult = entityViewService.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); - } else { - nonFilteredResult = entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); - } - List filteredEntityViews = nonFilteredResult.getData().stream().filter(entityView -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.READ, entityView.getId(), entityView); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - PageData filteredResult = new PageData<>(filteredEntityViews, - nonFilteredResult.getTotalPages(), - nonFilteredResult.getTotalElements(), - nonFilteredResult.hasNext()); - return checkNotNull(filteredResult); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + PageData nonFilteredResult; + if (type != null && type.trim().length() > 0) { + nonFilteredResult = entityViewService.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); + } else { + nonFilteredResult = entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); } + List filteredEntityViews = nonFilteredResult.getData().stream().filter(entityView -> { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.READ, entityView.getId(), entityView); + return true; + } catch (ThingsboardException e) { + return false; + } + }).collect(Collectors.toList()); + PageData filteredResult = new PageData<>(filteredEntityViews, + nonFilteredResult.getTotalPages(), + nonFilteredResult.getTotalElements(), + nonFilteredResult.hasNext()); + return checkNotNull(filteredResult); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index db040599fa..59fb18d82e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -248,14 +248,10 @@ public class EventController extends BaseController { @RequestBody EventFilter eventFilter) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); - checkEntityId(entityId, Operation.WRITE); + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + checkEntityId(entityId, Operation.WRITE); - eventService.removeEvents(getTenantId(), entityId, eventFilter, startTime, endTime); - } catch (Exception e) { - throw handleException(e); - } + eventService.removeEvents(getTenantId(), entityId, eventFilter, startTime, endTime); } private static EventType resolveEventType(String eventType) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.java b/application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.java index ca2101f145..2a05e9bf7c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.java +++ b/application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index 8a47ed2c15..ae1dec43fe 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,11 +64,7 @@ public class Lwm2mController extends BaseController { public LwM2MServerSecurityConfigDefault getLwm2mBootstrapSecurityInfo( @ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION) @PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException { - try { - return lwM2MService.getServerSecurityInfo(bootstrapServer); - } catch (Exception e) { - throw handleException(e); - } + return lwM2MService.getServerSecurityInfo(bootstrapServer); } @ApiOperation(hidden = true, value = "Save device with credentials (Deprecated)") diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java new file mode 100644 index 0000000000..6c27887e1a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -0,0 +1,323 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestInfo; +import org.thingsboard.server.common.data.notification.NotificationRequestPreview; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; +import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import javax.validation.Valid; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class NotificationController extends BaseController { + + private final NotificationService notificationService; + private final NotificationRequestService notificationRequestService; + private final NotificationTemplateService notificationTemplateService; + private final NotificationTargetService notificationTargetService; + private final NotificationCenter notificationCenter; + private final NotificationSettingsService notificationSettingsService; + + @ApiOperation(value = "Get notifications (getNotifications)", + notes = "**WebSocket API**:\n\n" + + "There are 2 types of subscriptions: one for unread notifications count, another for unread notifications themselves.\n\n" + + "The URI for opening WS session for notifications: `/api/ws/plugins/notifications`.\n\n" + + "Subscription command for unread notifications count:\n" + + "```\n{\n \"unreadCountSubCmd\": {\n \"cmdId\": 1234\n }\n}\n```\n" + + "To subscribe for latest unread notifications:\n" + + "```\n{\n \"unreadSubCmd\": {\n \"cmdId\": 1234,\n \"limit\": 10\n }\n}\n```\n" + + "To unsubscribe from any subscription:\n" + + "```\n{\n \"unsubCmd\": {\n \"cmdId\": 1234\n }\n}\n```\n" + + "To mark certain notifications as read, use following command:\n" + + "```\n{\n \"markAsReadCmd\": {\n \"cmdId\": 1234,\n \"notifications\": [\n \"6f860330-7fc2-11ed-b855-7dd3b7d2faa9\",\n \"5b6dfee0-8d0d-11ed-b61f-35a57b03dade\"\n ]\n }\n}\n\n```\n" + + "To mark all notifications as read:\n" + + "```\n{\n \"markAllAsReadCmd\": {\n \"cmdId\": 1234\n }\n}\n```\n" + + "\n\n" + + "Update structure for unread **notifications count subscription**:\n" + + "```\n{\n \"cmdId\": 1234,\n \"totalUnreadCount\": 55\n}\n```\n" + + "For **notifications subscription**:\n" + + "- full update of latest unread notifications:\n" + + "```\n{\n" + + " \"cmdId\": 1234,\n" + + " \"notifications\": [\n" + + " {\n" + + " \"id\": {\n" + + " \"entityType\": \"NOTIFICATION\",\n" + + " \"id\": \"6f860330-7fc2-11ed-b855-7dd3b7d2faa9\"\n" + + " },\n" + + " ...\n" + + " }\n" + + " ],\n" + + " \"totalUnreadCount\": 1\n" + + "}\n```\n" + + "- when new notification arrives or shown notification is updated:\n" + + "```\n{\n" + + " \"cmdId\": 1234,\n" + + " \"update\": {\n" + + " \"id\": {\n" + + " \"entityType\": \"NOTIFICATION\",\n" + + " \"id\": \"6f860330-7fc2-11ed-b855-7dd3b7d2faa9\"\n" + + " },\n" + + " # updated notification info, text, subject etc.\n" + + " ...\n" + + " },\n" + + " \"totalUnreadCount\": 2\n" + + "}\n```\n" + + "- when unread notifications count changes:\n" + + "```\n{\n" + + " \"cmdId\": 1234,\n" + + " \"totalUnreadCount\": 5\n" + + "}\n```") + @GetMapping("/notifications") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public PageData getNotifications(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(defaultValue = "false") boolean unreadOnly, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + // no permissions + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink); + } + + @PutMapping("/notification/{id}/read") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public void markNotificationAsRead(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + // no permissions + NotificationId notificationId = new NotificationId(id); + notificationCenter.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); + } + + @PutMapping("/notifications/read") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) { + // no permissions + notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId()); + } + + @DeleteMapping("/notification/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public void deleteNotification(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + // no permissions + NotificationId notificationId = new NotificationId(id); + notificationCenter.deleteNotification(user.getTenantId(), user.getId(), notificationId); + } + + @PostMapping("/notification/request") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequest createNotificationRequest(@RequestBody @Valid NotificationRequest notificationRequest, + @AuthenticationPrincipal SecurityUser user) throws Exception { + if (notificationRequest.getId() != null) { + throw new IllegalArgumentException("Notification request cannot be updated. You may only cancel/delete it"); + } + notificationRequest.setTenantId(user.getTenantId()); + checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION); + + notificationRequest.setOriginatorEntityId(user.getId()); + notificationRequest.setInfo(null); + notificationRequest.setRuleId(null); + notificationRequest.setStatus(null); + notificationRequest.setStats(null); + + return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, (tenantId, request) -> notificationCenter.processNotificationRequest(tenantId, request, null)); + } + + @PostMapping("/notification/request/preview") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequestPreview getNotificationRequestPreview(@RequestBody @Valid NotificationRequest request, + @RequestParam(defaultValue = "20") int recipientsPreviewSize, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + NotificationRequestPreview preview = new NotificationRequestPreview(); + + request.setOriginatorEntityId(user.getId()); + NotificationTemplate template; + if (request.getTemplateId() != null) { + template = checkEntityId(request.getTemplateId(), notificationTemplateService::findNotificationTemplateById, Operation.READ); + } else { + template = request.getTemplate(); + } + if (template == null) { + throw new IllegalArgumentException("Template is missing"); + } + NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder() + .tenantId(user.getTenantId()) + .request(request) + .template(template) + .settings(null) + .build(); + + Map processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream() + .collect(Collectors.toMap(m -> m, deliveryMethod -> { + NotificationRecipient recipient = null; + if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) { + recipient = userService.findUserById(user.getTenantId(), user.getId()); + } + return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, recipient); + })); + preview.setProcessedTemplates(processedTemplates); + + // generic permission + Set recipientsPreview = new LinkedHashSet<>(); + Map recipientsCountByTarget = new HashMap<>(); + + List targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(), + request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList())); + for (NotificationTarget target : targets) { + int recipientsCount; + List recipientsPart; + if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) { + PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), + (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize)); + recipientsCount = (int) recipients.getTotalElements(); + recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList()); + } else { + recipientsCount = 1; + recipientsPart = List.of(((SlackNotificationTargetConfig) target.getConfiguration()).getConversation()); + } + + for (NotificationRecipient recipient : recipientsPart) { + if (recipientsPreview.size() < recipientsPreviewSize) { + recipientsPreview.add(recipient.getTitle()); + } else { + break; + } + } + recipientsCountByTarget.put(target.getName(), recipientsCount); + } + + preview.setRecipientsPreview(recipientsPreview); + preview.setRecipientsCountByTarget(recipientsCountByTarget); + preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum()); + + return preview; + } + + @GetMapping("/notification/request/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequestInfo getNotificationRequestById(@PathVariable UUID id) throws ThingsboardException { + NotificationRequestId notificationRequestId = new NotificationRequestId(id); + return checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestInfoById, Operation.READ); + } + + @GetMapping("/notification/requests") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationRequests(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + // generic permission + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(user.getTenantId(), EntityType.USER, pageLink); + } + + @DeleteMapping("/notification/request/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationRequest(@PathVariable UUID id) throws Exception { + NotificationRequestId notificationRequestId = new NotificationRequestId(id); + NotificationRequest notificationRequest = checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationCenter::deleteNotificationRequest); + } + + + @PostMapping("/notification/settings") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationSettings saveNotificationSettings(@RequestBody @Valid NotificationSettings notificationSettings, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.WRITE); + TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId(); + notificationSettingsService.saveNotificationSettings(tenantId, notificationSettings); + return notificationSettings; + } + + @GetMapping("/notification/settings") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ); + TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId(); + return notificationSettingsService.findNotificationSettings(tenantId); + } + + @GetMapping("/notification/deliveryMethods") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public Set getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ); + return notificationCenter.getAvailableDeliveryMethods(user.getTenantId()); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java new file mode 100644 index 0000000000..4fdb0136f3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -0,0 +1,103 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; + +import javax.validation.Valid; +import java.util.UUID; + +import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION; + +@RestController +@TbCoreComponent +@RequestMapping("/api/notification") +@RequiredArgsConstructor +@Slf4j +public class NotificationRuleController extends BaseController { + + private final NotificationRuleService notificationRuleService; + + @PostMapping("/rule") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule, + @AuthenticationPrincipal SecurityUser user) throws Exception { + notificationRule.setTenantId(user.getTenantId()); + checkEntity(notificationRule.getId(), notificationRule, NOTIFICATION); + + NotificationRuleTriggerType triggerType = notificationRule.getTriggerType(); + if ((user.isTenantAdmin() && !triggerType.isTenantLevel()) || (user.isSystemAdmin() && triggerType.isTenantLevel())) { + throw new IllegalArgumentException("Trigger type " + triggerType + " is not available"); + } + + return doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule); + } + + @GetMapping("/rule/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRuleInfo getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException { + NotificationRuleId notificationRuleId = new NotificationRuleId(id); + return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ); + } + + @GetMapping("/rules") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationRules(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + // generic permission + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationRuleService.findNotificationRulesInfosByTenantId(user.getTenantId(), pageLink); + } + + @DeleteMapping("/rule/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationRule(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) throws Exception { + NotificationRuleId notificationRuleId = new NotificationRuleId(id); + NotificationRule notificationRule = checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::deleteNotificationRuleById); + tbClusterService.broadcastEntityStateChangeEvent(user.getTenantId(), notificationRuleId, ComponentLifecycleEvent.DELETED); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java new file mode 100644 index 0000000000..4fd6b93022 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -0,0 +1,199 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; +import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; +import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter; +import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; +import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; + +import javax.validation.Valid; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION; + +@RestController +@TbCoreComponent +@RequestMapping("/api/notification") +@RequiredArgsConstructor +@Slf4j +public class NotificationTargetController extends BaseController { + + private final NotificationTargetService notificationTargetService; + + @ApiOperation(value = "Save notification target (saveNotificationTarget)", + notes = "Create or update notification target.\n\n" + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PostMapping("/target") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTarget saveNotificationTarget(@RequestBody @Valid NotificationTarget notificationTarget, + @AuthenticationPrincipal SecurityUser user) throws Exception { + notificationTarget.setTenantId(user.getTenantId()); + checkEntity(notificationTarget.getId(), notificationTarget, NOTIFICATION); + + NotificationTargetConfig targetConfig = notificationTarget.getConfiguration(); + if (targetConfig.getType() == NotificationTargetType.PLATFORM_USERS) { + checkTargetUsers(user, targetConfig); + } + + return doSaveAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::saveNotificationTarget); + } + + @ApiOperation(value = "Get notification target by id (getNotificationTargetById)", + notes = "Fetch saved notification target by id." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @GetMapping("/target/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTarget getNotificationTargetById(@PathVariable UUID id) throws ThingsboardException { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.READ); + } + + @ApiOperation(value = "Get recipients for notification target config (getRecipientsForNotificationTargetConfig)", + notes = "Get the list (page) of recipients (users) for such notification target configuration." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PostMapping("/target/recipients") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getRecipientsForNotificationTargetConfig(@RequestBody NotificationTarget notificationTarget, + @RequestParam int pageSize, + @RequestParam int page, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + // generic permission + NotificationTargetConfig targetConfig = notificationTarget.getConfiguration(); + if (targetConfig.getType() == NotificationTargetType.PLATFORM_USERS) { + checkTargetUsers(user, targetConfig); + } else { + throw new IllegalArgumentException("Target type is not platform users"); + } + + PageLink pageLink = createPageLink(pageSize, page, null, null, null); + return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), (PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration(), pageLink); + } + + @GetMapping(value = "/targets", params = {"ids"}) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public List getNotificationTargetsByIds(@RequestParam("ids") UUID[] ids, + @AuthenticationPrincipal SecurityUser user) { + // generic permission + List targetsIds = Arrays.stream(ids).map(NotificationTargetId::new).collect(Collectors.toList()); + return notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(), targetsIds); + } + + @ApiOperation(value = "Get notification targets (getNotificationTargets)", + notes = "Fetch the page of notification targets owned by sysadmin or tenant." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @GetMapping("/targets") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationTargets(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + // generic permission + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationTargetService.findNotificationTargetsByTenantId(user.getTenantId(), pageLink); + } + + @GetMapping(value = "/targets", params = "notificationType") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationTargetsBySupportedNotificationType(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) NotificationType notificationType, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationTargetService.findNotificationTargetsByTenantIdAndSupportedNotificationType(user.getTenantId(), notificationType, pageLink); + } + + @ApiOperation(value = "Delete notification target by id (deleteNotificationTargetById)", + notes = "Delete notification target by its id.\n\n" + + "This target cannot be referenced by existing scheduled notification requests or any notification rules." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @DeleteMapping("/target/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationTargetById(@PathVariable UUID id) throws Exception { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + NotificationTarget notificationTarget = checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTargetById); + } + + private void checkTargetUsers(SecurityUser user, NotificationTargetConfig targetConfig) throws ThingsboardException { + if (user.isSystemAdmin()) { + return; + } + // generic permission for users + UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter(); + switch (usersFilter.getType()) { + case USER_LIST: + for (UUID recipientId : ((UserListFilter) usersFilter).getUsersIds()) { + checkUserId(new UserId(recipientId), Operation.READ); + } + break; + case CUSTOMER_USERS: + CustomerId customerId = new CustomerId(((CustomerUsersFilter) usersFilter).getCustomerId()); + checkEntityId(customerId, Operation.READ); + break; + case TENANT_ADMINISTRATORS: + if (CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantsIds()) || + CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantProfilesIds())) { + throw new AccessDeniedException(""); + } + break; + case SYSTEM_ADMINISTRATORS: + throw new AccessDeniedException(""); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java new file mode 100644 index 0000000000..751b139bd3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java @@ -0,0 +1,156 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; + +import javax.validation.Valid; +import java.util.List; +import java.util.UUID; + +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION; + +@RestController +@TbCoreComponent +@RequiredArgsConstructor +@RequestMapping("/api/notification") +public class NotificationTemplateController extends BaseController { + + private final NotificationTemplateService notificationTemplateService; + private final NotificationSettingsService notificationSettingsService; + private final SlackService slackService; + + @ApiOperation(value = "Save notification template (saveNotificationTemplate)", + notes = "Create or update notification template.\n\n" + + "Example:\n" + + "```\n{\n \"name\": \"Hello to all my users\",\n" + + " \"notificationType\": \"Message from administrator\",\n" + + " \"configuration\": {\n" + + " \"defaultTextTemplate\": \"Hello everyone\", # required if any of the templates' bodies is not set\n" + + " \"templates\": {\n" + + " \"PUSH\": {\n \"method\": \"PUSH\",\n \"body\": null # defaultTextTemplate will be used if body is not set\n },\n" + + " \"SMS\": {\n \"method\": \"SMS\",\n \"body\": null\n },\n" + + " \"EMAIL\": {\n \"method\": \"EMAIL\",\n \"body\": \"Non-default value for email notification: Hello everyone\",\n \"subject\": \"Message from administrator\"\n },\n" + + " \"SLACK\": {\n \"method\": \"SLACK\",\n \"body\": null,\n \"conversationType\": \"PUBLIC_CHANNEL\",\n \"conversationId\": \"U02LD7BJOU2\" # received from listSlackConversations API method\n }\n" + + " }\n" + + " }\n}\n```" + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PostMapping("/template") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTemplate saveNotificationTemplate(@RequestBody @Valid NotificationTemplate notificationTemplate) throws Exception { + notificationTemplate.setTenantId(getTenantId()); + checkEntity(notificationTemplate.getId(), notificationTemplate, NOTIFICATION); + return doSaveAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::saveNotificationTemplate); + } + + @ApiOperation(value = "Get notification template by id (getNotificationTemplateById)", + notes = "Fetch notification template by id." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @GetMapping("/template/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTemplate getNotificationTemplateById(@PathVariable UUID id) throws ThingsboardException { + NotificationTemplateId notificationTemplateId = new NotificationTemplateId(id); + return checkEntityId(notificationTemplateId, notificationTemplateService::findNotificationTemplateById, Operation.READ); + } + + @ApiOperation(value = "Get notification templates (getNotificationTemplates)", + notes = "Fetch the page of notification templates owned by sysadmin or tenant." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @GetMapping("/templates") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationTemplates(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) NotificationType[] notificationTypes, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + // generic permission + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (notificationTypes == null || notificationTypes.length == 0) { + notificationTypes = NotificationType.values(); + } + return notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes(user.getTenantId(), + List.of(notificationTypes), pageLink); + } + + @ApiOperation(value = "Delete notification template by id (deleteNotificationTemplateById", + notes = "Delete notification template by its id.\n\n" + + "This template cannot be referenced by existing scheduled notification requests or any notification rules." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @DeleteMapping("/template/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationTemplateById(@PathVariable UUID id) throws Exception { + NotificationTemplateId notificationTemplateId = new NotificationTemplateId(id); + NotificationTemplate notificationTemplate = checkEntityId(notificationTemplateId, notificationTemplateService::findNotificationTemplateById, Operation.DELETE); + doDeleteAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::deleteNotificationTemplateById); + } + + @ApiOperation(value = "List Slack conversations (listSlackConversations)", + notes = "List available Slack conversations by type to use in notification template.\n\n" + + "Slack must be configured in notification settings." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @GetMapping("/slack/conversations") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public List listSlackConversations(@RequestParam SlackConversationType type, + @RequestParam(required = false) String token, + @AuthenticationPrincipal SecurityUser user) { + // generic permission + if (StringUtils.isEmpty(token)) { + NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId()); + SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) + settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); + if (slackConfig == null) { + throw new IllegalArgumentException("Slack is not configured"); + } + token = slackConfig.getBotToken(); + } + + return slackService.listConversations(user.getTenantId(), token, type); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java index f08f8f380a..f547a5f0ad 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,12 +54,8 @@ public class OAuth2ConfigTemplateController extends BaseController { @RequestMapping(method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.WRITE); - return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.WRITE); + return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); } @ApiOperation(value = "Delete OAuth2 client registration template by id (deleteClientRegistrationTemplate)" + SYSTEM_AUTHORITY_PARAGRAPH, @@ -70,13 +66,9 @@ public class OAuth2ConfigTemplateController extends BaseController { public void deleteClientRegistrationTemplate(@ApiParam(value = "String representation of client registration template id to delete", example = "139b1f81-2f5d-11ec-9dbe-9b627e1a88f4") @PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); - try { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE); - OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); - oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE); + OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); } @ApiOperation(value = "Get the list of all OAuth2 client registration templates (getClientRegistrationTemplates)" + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, @@ -85,12 +77,8 @@ public class OAuth2ConfigTemplateController extends BaseController { @RequestMapping(method = RequestMethod.GET, produces = "application/json") @ResponseBody public List getClientRegistrationTemplates() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.READ); - return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.READ); + return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 5904fab60d..ef5739a481 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,25 +69,21 @@ public class OAuth2Controller extends BaseController { "If platform type is not one of allowable values - it will just be ignored", allowableValues = "WEB, ANDROID, IOS") @RequestParam(required = false) String platform) throws ThingsboardException { - try { - if (log.isDebugEnabled()) { - log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String header = headerNames.nextElement(); - log.debug("Header: {} {}", header, request.getHeader(header)); - } + if (log.isDebugEnabled()) { + log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + log.debug("Header: {} {}", header, request.getHeader(header)); } - PlatformType platformType = null; - if (StringUtils.isNotEmpty(platform)) { - try { - platformType = PlatformType.valueOf(platform); - } catch (Exception e) {} - } - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType); - } catch (Exception e) { - throw handleException(e); } + PlatformType platformType = null; + if (StringUtils.isNotEmpty(platform)) { + try { + platformType = PlatformType.valueOf(platform); + } catch (Exception e) {} + } + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType); } @ApiOperation(value = "Get current OAuth2 settings (getCurrentOAuth2Info)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @@ -95,12 +91,8 @@ public class OAuth2Controller extends BaseController { @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") @ResponseBody public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); - return oAuth2Service.findOAuth2Info(); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); + return oAuth2Service.findOAuth2Info(); } @ApiOperation(value = "Save OAuth2 settings (saveOAuth2Info)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @@ -108,13 +100,9 @@ public class OAuth2Controller extends BaseController { @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); - oAuth2Service.saveOAuth2Info(oauth2Info); - return oAuth2Service.findOAuth2Info(); - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); + oAuth2Service.saveOAuth2Info(oauth2Info); + return oAuth2Service.findOAuth2Info(); } @ApiOperation(value = "Get OAuth2 log in processing URL (getLoginProcessingUrl)", notes = "Returns the URL enclosed in " + @@ -125,12 +113,8 @@ public class OAuth2Controller extends BaseController { @RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET) @ResponseBody public String getLoginProcessingUrl() throws ThingsboardException { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); - return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; - } catch (Exception e) { - throw handleException(e); - } + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); + return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; } } diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java index 0040187f02..9b923e01dc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,24 +87,20 @@ public class OtaPackageController extends BaseController { public ResponseEntity downloadOtaPackage(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); - try { - OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); - OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ); - - if (otaPackage.hasUrl()) { - return ResponseEntity.badRequest().build(); - } + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ); - ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array()); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName()) - .header("x-filename", otaPackage.getFileName()) - .contentLength(resource.contentLength()) - .contentType(parseMediaType(otaPackage.getContentType())) - .body(resource); - } catch (Exception e) { - throw handleException(e); + if (otaPackage.hasUrl()) { + return ResponseEntity.badRequest().build(); } + + ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName()) + .header("x-filename", otaPackage.getFileName()) + .contentLength(resource.contentLength()) + .contentType(parseMediaType(otaPackage.getContentType())) + .body(resource); } @ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)", @@ -117,12 +113,8 @@ public class OtaPackageController extends BaseController { public OtaPackageInfo getOtaPackageInfoById(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); - try { - OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); - return checkNotNull(otaPackageService.findOtaPackageInfoById(getTenantId(), otaPackageId)); - } catch (Exception e) { - throw handleException(e); - } + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + return checkNotNull(otaPackageService.findOtaPackageInfoById(getTenantId(), otaPackageId)); } @ApiOperation(value = "Get OTA Package (getOtaPackageById)", @@ -135,12 +127,8 @@ public class OtaPackageController extends BaseController { public OtaPackage getOtaPackageById(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); - try { - OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); - return checkOtaPackageId(otaPackageId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + return checkOtaPackageId(otaPackageId, Operation.READ); } @ApiOperation(value = "Create Or Update OTA Package Info (saveOtaPackageInfo)", @@ -204,12 +192,8 @@ public class OtaPackageController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink)); } @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", @@ -235,13 +219,9 @@ public class OtaPackageController extends BaseController { @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("deviceProfileId", strDeviceProfileId); checkParameter("type", strType); - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), - new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink)); } @ApiOperation(value = "Delete OTA Package (deleteOtaPackage)", diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java index 3b37e6727f..3ba1df8c2a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java index 05cdf6170a..509e91e546 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java index 300d522dbe..28f7f44e5f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; +import org.thingsboard.server.exception.ToErrorResponseEntity; import javax.annotation.Nullable; import java.util.UUID; @@ -159,12 +159,8 @@ public class RpcV2Controller extends AbstractRpcController { @ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true) @PathVariable(RPC_ID) String strRpc) throws ThingsboardException { checkParameter("RpcId", strRpc); - try { - RpcId rpcId = new RpcId(UUID.fromString(strRpc)); - return checkRpcId(rpcId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + RpcId rpcId = new RpcId(UUID.fromString(strRpc)); + return checkRpcId(rpcId, Operation.READ); } @ApiOperation(value = "Get persistent RPC requests", notes = "Allows to query RPC calls for specific device using pagination." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @@ -187,43 +183,39 @@ public class RpcV2Controller extends AbstractRpcController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("DeviceId", strDeviceId); - try { - if (rpcStatus != null && rpcStatus.equals(RpcStatus.DELETED)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "RpcStatus: DELETED"); - } - - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId)); - final DeferredResult response = new DeferredResult<>(); + if (rpcStatus != null && rpcStatus.equals(RpcStatus.DELETED)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "RpcStatus: DELETED"); + } - accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable DeferredResult result) { - PageData rpcCalls; - if (rpcStatus != null) { - rpcCalls = rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink); - } else { - rpcCalls = rpcService.findAllByDeviceId(tenantId, deviceId, pageLink); - } - response.setResult(new ResponseEntity<>(rpcCalls, HttpStatus.OK)); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId)); + final DeferredResult response = new DeferredResult<>(); + + accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable DeferredResult result) { + PageData rpcCalls; + if (rpcStatus != null) { + rpcCalls = rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink); + } else { + rpcCalls = rpcService.findAllByDeviceId(tenantId, deviceId, pageLink); } + response.setResult(new ResponseEntity<>(rpcCalls, HttpStatus.OK)); + } - @Override - public void onFailure(Throwable e) { - ResponseEntity entity; - if (e instanceof ToErrorResponseEntity) { - entity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); - } else { - entity = new ResponseEntity(HttpStatus.UNAUTHORIZED); - } - response.setResult(entity); + @Override + public void onFailure(Throwable e) { + ResponseEntity entity; + if (e instanceof ToErrorResponseEntity) { + entity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); + } else { + entity = new ResponseEntity(HttpStatus.UNAUTHORIZED); } - })); - return response; - } catch (Exception e) { - throw handleException(e); - } + response.setResult(entity); + } + })); + return response; } @ApiOperation(value = "Delete persistent RPC", notes = "Deletes the persistent RPC request." + TENANT_AUTHORITY_PARAGRAPH) @@ -234,25 +226,21 @@ public class RpcV2Controller extends AbstractRpcController { @ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true) @PathVariable(RPC_ID) String strRpc) throws ThingsboardException { checkParameter("RpcId", strRpc); - try { - RpcId rpcId = new RpcId(UUID.fromString(strRpc)); - Rpc rpc = checkRpcId(rpcId, Operation.DELETE); - - if (rpc != null) { - if (rpc.getStatus().equals(RpcStatus.QUEUED)) { - RemoveRpcActorMsg removeMsg = new RemoveRpcActorMsg(getTenantId(), rpc.getDeviceId(), rpc.getUuidId()); - log.trace("[{}] Forwarding msg {} to queue actor!", rpc.getDeviceId(), rpc); - tbClusterService.pushMsgToCore(removeMsg, null); - } + RpcId rpcId = new RpcId(UUID.fromString(strRpc)); + Rpc rpc = checkRpcId(rpcId, Operation.DELETE); + + if (rpc != null) { + if (rpc.getStatus().equals(RpcStatus.QUEUED)) { + RemoveRpcActorMsg removeMsg = new RemoveRpcActorMsg(getTenantId(), rpc.getDeviceId(), rpc.getUuidId()); + log.trace("[{}] Forwarding msg {} to queue actor!", rpc.getDeviceId(), rpc); + tbClusterService.pushMsgToCore(removeMsg, null); + } - rpcService.deleteRpc(getTenantId(), rpcId); - rpc.setStatus(RpcStatus.DELETED); + rpcService.deleteRpc(getTenantId(), rpcId); + rpc.setStatus(RpcStatus.DELETED); - TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); - tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); - } - } catch (Exception e) { - throw handleException(e); + TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); } } } diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index dc74f36f86..81cf8be9fa 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -166,12 +167,8 @@ public class RuleChainController extends BaseController { @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); - try { - RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - return checkRuleChain(ruleChainId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + return checkRuleChain(ruleChainId, Operation.READ); } @ApiOperation(value = "Get Rule Chain output labels (getRuleChainOutputLabels)", @@ -184,13 +181,9 @@ public class RuleChainController extends BaseController { @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); - try { - RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - checkRuleChain(ruleChainId, Operation.READ); - return tbRuleChainService.getRuleChainOutputLabels(getTenantId(), ruleChainId); - } catch (Exception e) { - throw handleException(e); - } + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + checkRuleChain(ruleChainId, Operation.READ); + return tbRuleChainService.getRuleChainOutputLabels(getTenantId(), ruleChainId); } @ApiOperation(value = "Get output labels usage (getRuleChainOutputLabelsUsage)", @@ -203,13 +196,9 @@ public class RuleChainController extends BaseController { @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); - try { - RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - checkRuleChain(ruleChainId, Operation.READ); - return tbRuleChainService.getOutputLabelUsage(getCurrentUser().getTenantId(), ruleChainId); - } catch (Exception e) { - throw handleException(e); - } + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + checkRuleChain(ruleChainId, Operation.READ); + return tbRuleChainService.getOutputLabelUsage(getCurrentUser().getTenantId(), ruleChainId); } @ApiOperation(value = "Get Rule Chain (getRuleChainById)", @@ -221,13 +210,9 @@ public class RuleChainController extends BaseController { @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); - try { - RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - checkRuleChain(ruleChainId, Operation.READ); - return ruleChainService.loadRuleChainMetaData(getTenantId(), ruleChainId); - } catch (Exception e) { - throw handleException(e); - } + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + checkRuleChain(ruleChainId, Operation.READ); + return ruleChainService.loadRuleChainMetaData(getTenantId(), ruleChainId); } @ApiOperation(value = "Create Or Update Rule Chain (saveRuleChain)", @@ -319,17 +304,13 @@ public class RuleChainController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - RuleChainType type = RuleChainType.CORE; - if (typeStr != null && typeStr.trim().length() > 0) { - type = RuleChainType.valueOf(typeStr); - } - return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink)); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + RuleChainType type = RuleChainType.CORE; + if (typeStr != null && typeStr.trim().length() > 0) { + type = RuleChainType.valueOf(typeStr); } + return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink)); } @ApiOperation(value = "Delete rule chain (deleteRuleChain)", @@ -357,25 +338,21 @@ public class RuleChainController extends BaseController { @ApiParam(value = RULE_NODE_ID_PARAM_DESCRIPTION) @PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException { checkParameter(RULE_NODE_ID, strRuleNodeId); - try { - RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); - checkRuleNode(ruleNodeId, Operation.READ); - TenantId tenantId = getCurrentUser().getTenantId(); - List events = eventService.findLatestEvents(tenantId, ruleNodeId, EventType.DEBUG_RULE_NODE, 2); - JsonNode result = null; - if (events != null) { - for (EventInfo event : events) { - JsonNode body = event.getBody(); - if (body.has("type") && body.get("type").asText().equals("IN")) { - result = body; - break; - } + RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); + checkRuleNode(ruleNodeId, Operation.READ); + TenantId tenantId = getCurrentUser().getTenantId(); + List events = eventService.findLatestEvents(tenantId, ruleNodeId, EventType.DEBUG_RULE_NODE, 2); + JsonNode result = null; + if (events != null) { + for (EventInfo event : events) { + JsonNode body = event.getBody(); + if (body.has("type") && body.get("type").asText().equals("IN")) { + result = body; + break; } } - return result; - } catch (Exception e) { - throw handleException(e); } + return result; } @ApiOperation(value = "Is TBEL script executor enabled", @@ -396,74 +373,70 @@ public class RuleChainController extends BaseController { @ApiParam(value = "Script language: JS or TBEL") @RequestParam(required = false) ScriptLanguage scriptLang, @ApiParam(value = "Test JS request. See API call description above.") - @RequestBody JsonNode inputParams) throws ThingsboardException { + @RequestBody JsonNode inputParams) throws ThingsboardException, JsonProcessingException { + String script = inputParams.get("script").asText(); + String scriptType = inputParams.get("scriptType").asText(); + JsonNode argNamesJson = inputParams.get("argNames"); + String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class); + + String data = inputParams.get("msg").asText(); + JsonNode metadataJson = inputParams.get("metadata"); + Map metadata = objectMapper.convertValue(metadataJson, new TypeReference>() { + }); + String msgType = inputParams.get("msgType").asText(); + String output = ""; + String errorText = ""; + ScriptEngine engine = null; try { - String script = inputParams.get("script").asText(); - String scriptType = inputParams.get("scriptType").asText(); - JsonNode argNamesJson = inputParams.get("argNames"); - String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class); - - String data = inputParams.get("msg").asText(); - JsonNode metadataJson = inputParams.get("metadata"); - Map metadata = objectMapper.convertValue(metadataJson, new TypeReference>() { - }); - String msgType = inputParams.get("msgType").asText(); - String output = ""; - String errorText = ""; - ScriptEngine engine = null; - try { - if (scriptLang == null) { - scriptLang = ScriptLanguage.JS; - } - if (ScriptLanguage.JS.equals(scriptLang)) { - engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames); - } else { - if (tbelInvokeService == null) { - throw new IllegalArgumentException("TBEL script engine is disabled!"); - } - engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames); - } - TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); - switch (scriptType) { - case "update": - output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); - break; - case "generate": - output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); - break; - case "filter": - boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); - output = Boolean.toString(result); - break; - case "switch": - Set states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); - output = objectMapper.writeValueAsString(states); - break; - case "json": - JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); - output = objectMapper.writeValueAsString(json); - break; - case "string": - output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); - break; - default: - throw new IllegalArgumentException("Unsupported script type: " + scriptType); - } - } catch (Exception e) { - log.error("Error evaluating JS function", e); - errorText = e.getMessage(); - } finally { - if (engine != null) { - engine.destroy(); + if (scriptLang == null) { + scriptLang = ScriptLanguage.JS; + } + if (ScriptLanguage.JS.equals(scriptLang)) { + engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames); + } else { + if (tbelInvokeService == null) { + throw new IllegalArgumentException("TBEL script engine is disabled!"); } + engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames); + } + TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); + switch (scriptType) { + case "update": + output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); + break; + case "generate": + output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); + break; + case "filter": + boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); + output = Boolean.toString(result); + break; + case "switch": + Set states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); + output = objectMapper.writeValueAsString(states); + break; + case "json": + JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); + output = objectMapper.writeValueAsString(json); + break; + case "string": + output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); + break; + default: + throw new IllegalArgumentException("Unsupported script type: " + scriptType); } - ObjectNode result = objectMapper.createObjectNode(); - result.put("output", output); - result.put("error", errorText); - return result; } catch (Exception e) { - throw handleException(e); + log.error("Error evaluating JS function", e); + errorText = e.getMessage(); + } finally { + if (engine != null) { + engine.destroy(); + } } + ObjectNode result = objectMapper.createObjectNode(); + result.put("output", output); + result.put("error", errorText); + return result; } @ApiOperation(value = "Export Rule Chains", notes = "Exports all tenant rule chains as one JSON." + TENANT_AUTHORITY_PARAGRAPH) @@ -473,13 +446,9 @@ public class RuleChainController extends BaseController { public RuleChainData exportRuleChains( @ApiParam(value = "A limit of rule chains to export.", required = true) @RequestParam("limit") int limit) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = new PageLink(limit); - return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = new PageLink(limit); + return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink)); } @ApiOperation(value = "Import Rule Chains", notes = "Imports all tenant rule chains as one JSON." + TENANT_AUTHORITY_PARAGRAPH) @@ -491,19 +460,15 @@ public class RuleChainController extends BaseController { @RequestBody RuleChainData ruleChainData, @ApiParam(value = "Enables overwrite for existing rule chains with the same name.") @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - List importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite); - for (RuleChainImportResult importResult : importResults) { - if (importResult.getError() == null) { - tbClusterService.broadcastEntityStateChangeEvent(importResult.getTenantId(), importResult.getRuleChainId(), - importResult.isUpdated() ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); - } + TenantId tenantId = getCurrentUser().getTenantId(); + List importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite); + for (RuleChainImportResult importResult : importResults) { + if (importResult.getError() == null) { + tbClusterService.broadcastEntityStateChangeEvent(importResult.getTenantId(), importResult.getRuleChainId(), + importResult.isUpdated() ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); } - return importResults; - } catch (Exception e) { - throw handleException(e); } + return importResults; } private String msgToOutput(TbMsg msg) throws Exception { @@ -601,15 +566,11 @@ public class RuleChainController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = getCurrentUser().getTenantId(); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink)); } @ApiOperation(value = "Set Edge Template Root Rule Chain (setEdgeTemplateRootRuleChain)", @@ -661,17 +622,13 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/autoAssignToEdgeRuleChains", method = RequestMethod.GET) @ResponseBody public List getAutoAssignToEdgeRuleChains() throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - List result = new ArrayList<>(); - PageDataIterableByTenant autoAssignRuleChainsIterator = - new PageDataIterableByTenant<>(ruleChainService::findAutoAssignToEdgeRuleChainsByTenantId, tenantId, DEFAULT_PAGE_SIZE); - for (RuleChain ruleChain : autoAssignRuleChainsIterator) { - result.add(ruleChain); - } - return checkNotNull(result); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = getCurrentUser().getTenantId(); + List result = new ArrayList<>(); + PageDataIterableByTenant autoAssignRuleChainsIterator = + new PageDataIterableByTenant<>(ruleChainService::findAutoAssignToEdgeRuleChainsByTenantId, tenantId, DEFAULT_PAGE_SIZE); + for (RuleChain ruleChain : autoAssignRuleChainsIterator) { + result.add(ruleChain); } + return checkNotNull(result); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index 8cb7bce6ab..19195c7681 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index ec0b530117..d94dc31fa1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,20 +82,16 @@ public class TbResourceController extends BaseController { public ResponseEntity downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); - try { - TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); - TbResource tbResource = checkResourceId(resourceId, Operation.READ); + TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); + TbResource tbResource = checkResourceId(resourceId, Operation.READ); - ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) - .header("x-filename", tbResource.getFileName()) - .contentLength(resource.contentLength()) - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(resource); - } catch (Exception e) { - throw handleException(e); - } + ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) + .header("x-filename", tbResource.getFileName()) + .contentLength(resource.contentLength()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); } @ApiOperation(value = "Get Resource Info (getResourceInfoById)", @@ -108,12 +104,8 @@ public class TbResourceController extends BaseController { public TbResourceInfo getResourceInfoById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); - try { - TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); - return checkResourceInfoId(resourceId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); + return checkResourceInfoId(resourceId, Operation.READ); } @ApiOperation(value = "Get Resource (getResourceById)", @@ -126,12 +118,8 @@ public class TbResourceController extends BaseController { public TbResource getResourceById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); - try { - TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); - return checkResourceId(resourceId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); + return checkResourceId(resourceId, Operation.READ); } @ApiOperation(value = "Create Or Update Resource (saveResource)", @@ -171,15 +159,11 @@ public class TbResourceController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink)); - } else { - return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink)); - } - } catch (Exception e) { - throw handleException(e); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { + return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink)); + } else { + return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink)); } } @@ -200,12 +184,8 @@ public class TbResourceController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = new PageLink(pageSize, page, textSearch); - return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = new PageLink(pageSize, page, textSearch); + return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink)); } @ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)", @@ -221,11 +201,7 @@ public class TbResourceController extends BaseController { @RequestParam String sortProperty, @ApiParam(value = "LwM2M Object ids.", required = true) @RequestParam(required = false) String[] objectIds) throws ThingsboardException { - try { - return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds)); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds)); } @ApiOperation(value = "Delete Resource (deleteResource)", diff --git a/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java b/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java index afc6f38402..766b325b0c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index f843e891be..ce6537fd3f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,8 +83,8 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.AttributeData; import org.thingsboard.server.service.telemetry.TsData; -import org.thingsboard.server.service.telemetry.exception.InvalidParametersException; -import org.thingsboard.server.service.telemetry.exception.UncheckedApiException; +import org.thingsboard.server.exception.InvalidParametersException; +import org.thingsboard.server.exception.UncheckedApiException; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -184,11 +184,7 @@ public class TelemetryController extends BaseController { public DeferredResult getAttributeKeys( @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { - try { - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback); - } catch (Exception e) { - throw handleException(e); - } + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback); } @ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)", @@ -205,12 +201,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException { - try { - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); - } catch (Exception e) { - throw handleException(e); - } } @ApiOperation(value = "Get attributes (getAttributes)", @@ -228,13 +220,9 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { - try { SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); - } catch (Exception e) { - throw handleException(e); - } } @@ -256,13 +244,9 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { - try { - SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, + SecurityUser user = getCurrentUser(); + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); - } catch (Exception e) { - throw handleException(e); - } } @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", @@ -275,12 +259,8 @@ public class TelemetryController extends BaseController { public DeferredResult getTimeseriesKeys( @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { - try { - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); - } catch (Exception e) { - throw handleException(e); - } } @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)", @@ -305,13 +285,9 @@ public class TelemetryController extends BaseController { @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr, @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { - try { - SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes)); - } catch (Exception e) { - throw handleException(e); - } + SecurityUser user = getCurrentUser(); + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, + (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes)); } @ApiOperation(value = "Get time-series data (getTimeseries)", @@ -348,19 +324,15 @@ public class TelemetryController extends BaseController { @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy, @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { - try { - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> { - // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted - Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr); - List queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy)) - .collect(Collectors.toList()); - - Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); - }); - } catch (Exception e) { - throw handleException(e); - } + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, + (result, tenantId, entityId) -> { + // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted + Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr); + List queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy)) + .collect(Collectors.toList()); + + Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); + }); } @ApiOperation(value = "Save device attributes (saveDeviceAttributes)", @@ -384,12 +356,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); - return saveAttributes(getTenantId(), entityId, scope, request); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); + return saveAttributes(getTenantId(), entityId, scope, request); } @ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)", @@ -412,12 +380,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return saveAttributes(getTenantId(), entityId, scope, request); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return saveAttributes(getTenantId(), entityId, scope, request); } @ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)", @@ -440,12 +404,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return saveAttributes(getTenantId(), entityId, scope, request); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return saveAttributes(getTenantId(), entityId, scope, request); } @@ -469,12 +429,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return saveTelemetry(getTenantId(), entityId, requestBody, 0L); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return saveTelemetry(getTenantId(), entityId, requestBody, 0L); } @ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)", @@ -499,12 +455,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl, @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return saveTelemetry(getTenantId(), entityId, requestBody, ttl); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return saveTelemetry(getTenantId(), entityId, requestBody, ttl); } @ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)", @@ -537,12 +489,8 @@ public class TelemetryController extends BaseController { @RequestParam(name = "endTs", required = false) Long endTs, @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted); } private DeferredResult deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys, @@ -607,12 +555,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); - return deleteAttributes(entityId, scope, keysStr); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); + return deleteAttributes(entityId, scope, keysStr); } @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)", @@ -635,12 +579,8 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { - try { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return deleteAttributes(entityId, scope, keysStr); - } catch (Exception e) { - throw handleException(e); - } + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return deleteAttributes(entityId, scope, keysStr); } private DeferredResult deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index b44c5327cd..6574969e15 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,16 +79,12 @@ public class TenantController extends BaseController { @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); - try { - TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); - Tenant tenant = checkTenantId(tenantId, Operation.READ); - if (!tenant.getAdditionalInfo().isNull()) { - processDashboardIdFromAdditionalInfo((ObjectNode) tenant.getAdditionalInfo(), HOME_DASHBOARD); - } - return tenant; - } catch (Exception e) { - throw handleException(e); + TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); + Tenant tenant = checkTenantId(tenantId, Operation.READ); + if (!tenant.getAdditionalInfo().isNull()) { + processDashboardIdFromAdditionalInfo((ObjectNode) tenant.getAdditionalInfo(), HOME_DASHBOARD); } + return tenant; } @ApiOperation(value = "Get Tenant Info (getTenantInfoById)", @@ -101,12 +97,8 @@ public class TenantController extends BaseController { @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); - try { - TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); - return checkTenantInfoId(tenantId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); + return checkTenantInfoId(tenantId, Operation.READ); } @ApiOperation(value = "Create Or update Tenant (saveTenant)", @@ -154,12 +146,8 @@ public class TenantController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(tenantService.findTenants(pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(tenantService.findTenants(pageLink)); } @ApiOperation(value = "Get Tenants Info (getTenants)", notes = "Returns a page of tenant info objects registered in the platform. " @@ -179,12 +167,8 @@ public class TenantController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder ) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(tenantService.findTenantInfos(pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(tenantService.findTenantInfos(pageLink)); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index e5a33da948..15c0e46836 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -32,14 +34,19 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.List; +import java.util.UUID; + import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; @@ -75,12 +82,8 @@ public class TenantProfileController extends BaseController { @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); - try { - TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); - return checkTenantProfileId(tenantProfileId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); + return checkTenantProfileId(tenantProfileId, Operation.READ); } @ApiOperation(value = "Get Tenant Profile Info (getTenantProfileInfoById)", @@ -92,12 +95,8 @@ public class TenantProfileController extends BaseController { @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); - try { - TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); - return checkNotNull(tenantProfileService.findTenantProfileInfoById(getTenantId(), tenantProfileId)); - } catch (Exception e) { - throw handleException(e); - } + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); + return checkNotNull(tenantProfileService.findTenantProfileInfoById(getTenantId(), tenantProfileId)); } @ApiOperation(value = "Get default Tenant Profile Info (getDefaultTenantProfileInfo)", @@ -106,11 +105,7 @@ public class TenantProfileController extends BaseController { @RequestMapping(value = "/tenantProfileInfo/default", method = RequestMethod.GET) @ResponseBody public EntityInfo getDefaultTenantProfileInfo() throws ThingsboardException { - try { - return checkNotNull(tenantProfileService.findDefaultTenantProfileInfo(getTenantId())); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(tenantProfileService.findDefaultTenantProfileInfo(getTenantId())); } @ApiOperation(value = "Create Or update Tenant Profile (saveTenantProfile)", @@ -171,19 +166,15 @@ public class TenantProfileController extends BaseController { @ResponseBody public TenantProfile saveTenantProfile(@ApiParam(value = "A JSON value representing the tenant profile.") @RequestBody TenantProfile tenantProfile) throws ThingsboardException { - try { - TenantProfile oldProfile; - if (tenantProfile.getId() == null) { - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE); - oldProfile = null; - } else { - oldProfile = checkTenantProfileId(tenantProfile.getId(), Operation.WRITE); - } - - return tbTenantProfileService.save(getTenantId(), tenantProfile, oldProfile); - } catch (Exception e) { - throw handleException(e); + TenantProfile oldProfile; + if (tenantProfile.getId() == null) { + accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE); + oldProfile = null; + } else { + oldProfile = checkTenantProfileId(tenantProfile.getId(), Operation.WRITE); } + + return tbTenantProfileService.save(getTenantId(), tenantProfile, oldProfile); } @ApiOperation(value = "Delete Tenant Profile (deleteTenantProfile)", @@ -193,14 +184,10 @@ public class TenantProfileController extends BaseController { @ResponseStatus(value = HttpStatus.OK) public void deleteTenantProfile(@ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { - try { - checkParameter("tenantProfileId", strTenantProfileId); - TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); - TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE); - tbTenantProfileService.delete(getTenantId(), profile); - } catch (Exception e) { - throw handleException(e); - } + checkParameter("tenantProfileId", strTenantProfileId); + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); + TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE); + tbTenantProfileService.delete(getTenantId(), profile); } @ApiOperation(value = "Make tenant profile default (setDefaultTenantProfile)", @@ -212,14 +199,10 @@ public class TenantProfileController extends BaseController { @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); - try { - TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); - TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE); - tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId); - return tenantProfile; - } catch (Exception e) { - throw handleException(e); - } + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); + TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE); + tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId); + return tenantProfile; } @ApiOperation(value = "Get Tenant Profiles (getTenantProfiles)", notes = "Returns a page of tenant profiles registered in the platform. " + PAGE_DATA_PARAMETERS + SYSTEM_AUTHORITY_PARAGRAPH) @@ -237,12 +220,8 @@ public class TenantProfileController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink)); } @ApiOperation(value = "Get Tenant Profiles Info (getTenantProfileInfos)", notes = "Returns a page of tenant profile info objects registered in the platform. " @@ -261,11 +240,15 @@ public class TenantProfileController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink)); + } + + @GetMapping(value = "/tenantProfiles", params = {"ids"}) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + public List getTenantProfilesByIds(@RequestParam("ids") UUID[] ids) { + return tenantProfileService.findTenantProfilesByIds(TenantId.SYS_TENANT_ID, ids); } + + } diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java index 5aad5c9503..b07b1eb70b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java index 5ce46e324e..39b352633c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java b/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java index 321ff8da5c..a40d3b85af 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/UsageInfoController.java b/application/src/main/java/org/thingsboard/server/controller/UsageInfoController.java new file mode 100644 index 0000000000..a7d4250b65 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/UsageInfoController.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.UsageInfo; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.dao.usage.UsageInfoService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@Slf4j +public class UsageInfoController extends BaseController { + + @Autowired + private UsageInfoService usageInfoService; + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/usage", method = RequestMethod.GET) + @ResponseBody + public UsageInfo getTenantUsageInfo() throws ThingsboardException { + return checkNotNull(usageInfoService.getUsageInfo(getCurrentUser().getTenantId())); + } +} diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 86e4456638..cedb2d6726 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,20 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -32,21 +36,40 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.UserEmailInfo; +import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityTypeFilter; +import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; +import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; +import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; -import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.service.query.EntityQueryService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; @@ -55,9 +78,17 @@ import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; +import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; @@ -84,11 +115,11 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI public class UserController extends BaseController { public static final String USER_ID = "userId"; + public static final String PATHS = "paths"; public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s"; @Value("${security.user_token_access_enabled}") - @Getter private boolean userTokenAccessEnabled; private final MailService mailService; @@ -97,6 +128,12 @@ public class UserController extends BaseController { private final ApplicationEventPublisher eventPublisher; private final TbUserService tbUserService; + @Autowired + private EntityQueryService entityQueryService; + + @Autowired + private EntityService entityService; + @ApiOperation(value = "Get User (getUserById)", notes = "Fetch the User object based on the provided User Id. " + "If the user has the authority of 'SYS_ADMIN', the server does not perform additional checks. " + @@ -109,22 +146,18 @@ public class UserController extends BaseController { @ApiParam(value = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); - try { - UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId, Operation.READ); - if (user.getAdditionalInfo().isObject()) { - ObjectNode additionalInfo = (ObjectNode) user.getAdditionalInfo(); - processDashboardIdFromAdditionalInfo(additionalInfo, DEFAULT_DASHBOARD); - processDashboardIdFromAdditionalInfo(additionalInfo, HOME_DASHBOARD); - UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); - if (userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { - additionalInfo.put("userCredentialsEnabled", true); - } + UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.READ); + if (user.getAdditionalInfo().isObject()) { + ObjectNode additionalInfo = (ObjectNode) user.getAdditionalInfo(); + processDashboardIdFromAdditionalInfo(additionalInfo, DEFAULT_DASHBOARD); + processDashboardIdFromAdditionalInfo(additionalInfo, HOME_DASHBOARD); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); + if (userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { + additionalInfo.put("userCredentialsEnabled", true); } - return user; - } catch (Exception e) { - throw handleException(e); } + return user; } @ApiOperation(value = "Check Token Access Enabled (isUserTokenAccessEnabled)", @@ -149,21 +182,17 @@ public class UserController extends BaseController { @ApiParam(value = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); - try { - if (!userTokenAccessEnabled) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - UserId userId = new UserId(toUUID(strUserId)); - SecurityUser authUser = getCurrentUser(); - User user = checkUserId(userId, Operation.READ); - UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); - UserCredentials credentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), userId); - SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); - return tokenFactory.createTokenPair(securityUser); - } catch (Exception e) { - throw handleException(e); + if (!userTokenAccessEnabled) { + throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, + ThingsboardErrorCode.PERMISSION_DENIED); } + UserId userId = new UserId(toUUID(strUserId)); + SecurityUser authUser = getCurrentUser(); + User user = checkUserId(userId, Operation.READ); + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); + UserCredentials credentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), userId); + SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); + return tokenFactory.createTokenPair(securityUser); } @ApiOperation(value = "Save Or update User (saveUser)", @@ -198,23 +227,19 @@ public class UserController extends BaseController { @ApiParam(value = "Email of the user", required = true) @RequestParam(value = "email") String email, HttpServletRequest request) throws ThingsboardException { - try { - User user = checkNotNull(userService.findUserByEmail(getCurrentUser().getTenantId(), email)); + User user = checkNotNull(userService.findUserByEmail(getCurrentUser().getTenantId(), email)); - accessControlService.checkPermission(getCurrentUser(), Resource.USER, Operation.READ, - user.getId(), user); + accessControlService.checkPermission(getCurrentUser(), Resource.USER, Operation.READ, + user.getId(), user); - UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); - if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) { - String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); - String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, - userCredentials.getActivateToken()); - mailService.sendActivationEmail(activateUrl, email); - } else { - throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - } catch (Exception e) { - throw handleException(e); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) { + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); + String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, + userCredentials.getActivateToken()); + mailService.sendActivationEmail(activateUrl, email); + } else { + throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } } @@ -229,21 +254,17 @@ public class UserController extends BaseController { @PathVariable(USER_ID) String strUserId, HttpServletRequest request) throws ThingsboardException { checkParameter(USER_ID, strUserId); - try { - UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId, Operation.READ); - SecurityUser authUser = getCurrentUser(); - UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); - if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) { - String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); - String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, - userCredentials.getActivateToken()); - return activateUrl; - } else { - throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - } catch (Exception e) { - throw handleException(e); + UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.READ); + SecurityUser authUser = getCurrentUser(); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) { + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); + String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, + userCredentials.getActivateToken()); + return activateUrl; + } else { + throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } } @@ -282,19 +303,53 @@ public class UserController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - SecurityUser currentUser = getCurrentUser(); - if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { - return checkNotNull(userService.findUsersByTenantId(currentUser.getTenantId(), pageLink)); - } else { - return checkNotNull(userService.findCustomerUsers(currentUser.getTenantId(), currentUser.getCustomerId(), pageLink)); - } - } catch (Exception e) { - throw handleException(e); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + SecurityUser currentUser = getCurrentUser(); + if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { + return checkNotNull(userService.findUsersByTenantId(currentUser.getTenantId(), pageLink)); + } else { + return checkNotNull(userService.findCustomerUsers(currentUser.getTenantId(), currentUser.getCustomerId(), pageLink)); } } + @ApiOperation(value = "Find users by query (findUsersByQuery)", + notes = "Returns page of user data objects. Search is been executed by email, firstName and " + + "lastName fields. " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/users/info", method = RequestMethod.GET) + @ResponseBody + public PageData findUsersByQuery( + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + SecurityUser securityUser = getCurrentUser(); + + EntityTypeFilter entityFilter = new EntityTypeFilter(); + entityFilter.setEntityType(EntityType.USER); + EntityDataPageLink pageLink = new EntityDataPageLink(pageSize, page, textSearch, createEntityDataSortOrder(sortProperty, sortOrder)); + List entityFields = Arrays.asList(new EntityKey(ENTITY_FIELD, "firstName"), + new EntityKey(ENTITY_FIELD, "lastName"), + new EntityKey(ENTITY_FIELD, "email")); + + EntityDataQuery query = new EntityDataQuery(entityFilter, pageLink, entityFields, null, null); + + return entityQueryService.findEntityDataByQuery(securityUser, query).mapData(entityData -> + { + Map fieldValues = entityData.getLatest().get(ENTITY_FIELD); + return new UserEmailInfo(UserId.fromString(entityData.getEntityId().getId().toString()), + fieldValues.get("email").getValue(), + fieldValues.get("firstName").getValue(), + fieldValues.get("lastName").getValue()); + }); + } + @ApiOperation(value = "Get Tenant Users (getTenantAdmins)", notes = "Returns a page of users owned by tenant. " + PAGE_DATA_PARAMETERS + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @@ -314,13 +369,9 @@ public class UserController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("tenantId", strTenantId); - try { - TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(userService.findTenantAdmins(tenantId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(userService.findTenantAdmins(tenantId, pageLink)); } @ApiOperation(value = "Get Customer Users (getCustomerUsers)", @@ -342,15 +393,11 @@ public class UserController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId, Operation.READ); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink)); - } catch (Exception e) { - throw handleException(e); - } + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink)); } @ApiOperation(value = "Enable/Disable User credentials (setUserCredentialsEnabled)", @@ -364,18 +411,188 @@ public class UserController extends BaseController { @ApiParam(value = "Disable (\"true\") or enable (\"false\") the credentials.", defaultValue = "true") @RequestParam(required = false, defaultValue = "true") boolean userCredentialsEnabled) throws ThingsboardException { checkParameter(USER_ID, strUserId); - try { - UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId, Operation.WRITE); - TenantId tenantId = getCurrentUser().getTenantId(); - userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled); + UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.WRITE); + TenantId tenantId = getCurrentUser().getTenantId(); + userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled); + + if (!userCredentialsEnabled) { + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(userId)); + } + } - if (!userCredentialsEnabled) { - eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(userId)); + @ApiOperation(value = "Get usersForAssign (getUsersForAssign)", + notes = "Returns page of user data objects that can be assigned to provided alarmId. " + + "Search is been executed by email, firstName and lastName fields. " + + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/users/assign/{alarmId}", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getUsersForAssign( + @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION, required = true) + @PathVariable("alarmId") String strAlarmId, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + checkParameter("alarmId", strAlarmId); + AlarmId alarmEntityId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmEntityId, Operation.READ); + SecurityUser currentUser = getCurrentUser(); + TenantId tenantId = currentUser.getTenantId(); + CustomerId originatorCustomerId = entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + PageData pageData; + if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { + if (alarm.getCustomerId() == null) { + pageData = userService.findTenantAdmins(tenantId, pageLink); + } else { + ArrayList customerIds = new ArrayList<>(Collections.singletonList(new CustomerId(CustomerId.NULL_UUID))); + if (!CustomerId.NULL_UUID.equals(originatorCustomerId.getId())) { + customerIds.add(originatorCustomerId); + } + pageData = userService.findUsersByCustomerIds(tenantId, customerIds, pageLink); + } + } else { + pageData = userService.findCustomerUsers(tenantId, alarm.getCustomerId(), pageLink); } + return pageData.mapData(user -> new UserEmailInfo(user.getId(), user.getEmail(), user.getFirstName(), user.getLastName())); } catch (Exception e) { throw handleException(e); } } + @ApiOperation(value = "Save user settings (saveUserSettings)", + notes = "Save user settings represented in json format for authorized user. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PostMapping(value = "/user/settings") + public JsonNode saveUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + + UserSettings userSettings = new UserSettings(); + userSettings.setType(UserSettingsType.GENERAL); + userSettings.setSettings(settings); + userSettings.setUserId(currentUser.getId()); + return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings(); + } + + @ApiOperation(value = "Update user settings (saveUserSettings)", + notes = "Update user settings for authorized user. Only specified json elements will be updated." + + "Example: you have such settings: {A:5, B:{C:10, D:20}}. Updating it with {B:{C:10, D:30}} will result in" + + "{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PutMapping(value = "/user/settings") + public void putUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, settings); + } + + @ApiOperation(value = "Get user settings (getUserSettings)", + notes = "Fetch the User settings based on authorized user. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/user/settings") + public JsonNode getUserSettings() throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + + UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL); + return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings(); + } + + @ApiOperation(value = "Delete user settings (deleteUserSettings)", + notes = "Delete user settings by specifying list of json element xpaths. \n " + + "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/user/settings/{paths}", method = RequestMethod.DELETE) + public void deleteUserSettings(@ApiParam(value = PATHS) + @PathVariable(PATHS) String paths) throws ThingsboardException { + checkParameter(USER_ID, paths); + + SecurityUser currentUser = getCurrentUser(); + userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, Arrays.asList(paths.split(","))); + } + + @ApiOperation(value = "Update user settings (saveUserSettings)", + notes = "Update user settings for authorized user. Only specified json elements will be updated." + + "Example: you have such settings: {A:5, B:{C:10, D:20}}. Updating it with {B:{C:10, D:30}} will result in" + + "{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PutMapping(value = "/user/settings/{type}") + public void putUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") + @PathVariable("type") String strType, @RequestBody JsonNode settings) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); + checkNotReserved(strType, type); + userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), type, settings); + } + + @ApiOperation(value = "Get user settings (getUserSettings)", + notes = "Fetch the User settings based on authorized user. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/user/settings/{type}") + public JsonNode getUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") + @PathVariable("type") String strType) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); + checkNotReserved(strType, type); + UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), type); + return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings(); + } + + @ApiOperation(value = "Delete user settings (deleteUserSettings)", + notes = "Delete user settings by specifying list of json element xpaths. \n " + + "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/user/settings/{type}/{paths}", method = RequestMethod.DELETE) + public void deleteUserSettings(@ApiParam(value = PATHS) + @PathVariable(PATHS) String paths, + @ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") + @PathVariable("type") String strType) throws ThingsboardException { + checkParameter(USER_ID, paths); + UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); + checkNotReserved(strType, type); + SecurityUser currentUser = getCurrentUser(); + userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), type, Arrays.asList(paths.split(","))); + } + + @ApiOperation(value = "Get information about last visited and starred dashboards (getLastVisitedDashboards)", + notes = "Fetch the list of last visited and starred dashboards. Both lists are limited to 10 items." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/user/dashboards") + public UserDashboardsInfo getUserDashboardsInfo() throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + return userSettingsService.findUserDashboardsInfo(currentUser.getTenantId(), currentUser.getId()); + } + + @ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)", + notes = "Report action of User over the dashboard. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.GET) + @ResponseBody + public UserDashboardsInfo reportUserDashboardAction( + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @PathVariable(DashboardController.DASHBOARD_ID) String strDashboardId, + @ApiParam(value = "Dashboard action, one of: \"visit\", \"star\" or \"unstar\".") + @PathVariable("action") String strAction) throws ThingsboardException { + checkParameter(DashboardController.DASHBOARD_ID, strDashboardId); + checkParameter("action", strAction); + UserDashboardAction action = checkEnumParameter("Action", strAction, UserDashboardAction::valueOf); + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + checkDashboardInfoId(dashboardId, Operation.READ); + SecurityUser currentUser = getCurrentUser(); + return userSettingsService.reportUserDashboardAction(currentUser.getTenantId(), currentUser.getId(), dashboardId, action); + } + + private void checkNotReserved(String strType, UserSettingsType type) throws ThingsboardException { + if (type.isReserved()) { + throw new ThingsboardException("Settings with type: " + strType + " are reserved for internal use!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index 725d8ca2b4..d1ecd0441a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,12 +70,8 @@ public class WidgetTypeController extends AutoCommitController { @ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { checkParameter("widgetTypeId", strWidgetTypeId); - try { - WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - return checkWidgetTypeId(widgetTypeId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); + return checkWidgetTypeId(widgetTypeId, Operation.READ); } @ApiOperation(value = "Create Or Update Widget Type (saveWidgetType)", @@ -93,32 +89,28 @@ public class WidgetTypeController extends AutoCommitController { @ResponseBody public WidgetTypeDetails saveWidgetType( @ApiParam(value = "A JSON value representing the Widget Type Details.", required = true) - @RequestBody WidgetTypeDetails widgetTypeDetails) throws ThingsboardException { - try { - var currentUser = getCurrentUser(); - if (Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { - widgetTypeDetails.setTenantId(TenantId.SYS_TENANT_ID); - } else { - widgetTypeDetails.setTenantId(currentUser.getTenantId()); - } + @RequestBody WidgetTypeDetails widgetTypeDetails) throws Exception { + var currentUser = getCurrentUser(); + if (Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { + widgetTypeDetails.setTenantId(TenantId.SYS_TENANT_ID); + } else { + widgetTypeDetails.setTenantId(currentUser.getTenantId()); + } - checkEntity(widgetTypeDetails.getId(), widgetTypeDetails, Resource.WIDGET_TYPE); - WidgetTypeDetails savedWidgetTypeDetails = widgetTypeService.saveWidgetType(widgetTypeDetails); + checkEntity(widgetTypeDetails.getId(), widgetTypeDetails, Resource.WIDGET_TYPE); + WidgetTypeDetails savedWidgetTypeDetails = widgetTypeService.saveWidgetType(widgetTypeDetails); - if (!Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId(), widgetTypeDetails.getBundleAlias()); - if (widgetsBundle != null) { - autoCommit(currentUser, widgetsBundle.getId()); - } + if (!Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId(), widgetTypeDetails.getBundleAlias()); + if (widgetsBundle != null) { + autoCommit(currentUser, widgetsBundle.getId()); } + } - sendEntityNotificationMsg(getTenantId(), savedWidgetTypeDetails.getId(), - widgetTypeDetails.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); + sendEntityNotificationMsg(getTenantId(), savedWidgetTypeDetails.getId(), + widgetTypeDetails.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); - return checkNotNull(savedWidgetTypeDetails); - } catch (Exception e) { - throw handleException(e); - } + return checkNotNull(savedWidgetTypeDetails); } @ApiOperation(value = "Delete widget type (deleteWidgetType)", @@ -128,26 +120,21 @@ public class WidgetTypeController extends AutoCommitController { @ResponseStatus(value = HttpStatus.OK) public void deleteWidgetType( @ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true) - @PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { + @PathVariable("widgetTypeId") String strWidgetTypeId) throws Exception { checkParameter("widgetTypeId", strWidgetTypeId); - try { - var currentUser = getCurrentUser(); - WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - WidgetTypeDetails wtd = checkWidgetTypeId(widgetTypeId, Operation.DELETE); - widgetTypeService.deleteWidgetType(currentUser.getTenantId(), widgetTypeId); - - if (wtd != null && !Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(wtd.getTenantId(), wtd.getBundleAlias()); - if (widgetsBundle != null) { - autoCommit(currentUser, widgetsBundle.getId()); - } + var currentUser = getCurrentUser(); + WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); + WidgetTypeDetails wtd = checkWidgetTypeId(widgetTypeId, Operation.DELETE); + widgetTypeService.deleteWidgetType(currentUser.getTenantId(), widgetTypeId); + + if (wtd != null && !Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(wtd.getTenantId(), wtd.getBundleAlias()); + if (widgetsBundle != null) { + autoCommit(currentUser, widgetsBundle.getId()); } - - sendEntityNotificationMsg(getTenantId(), widgetTypeId, EdgeEventActionType.DELETED); - - } catch (Exception e) { - throw handleException(e); } + + sendEntityNotificationMsg(getTenantId(), widgetTypeId, EdgeEventActionType.DELETED); } @ApiOperation(value = "Get all Widget types for specified Bundle (getBundleWidgetTypes)", @@ -160,17 +147,13 @@ public class WidgetTypeController extends AutoCommitController { @RequestParam boolean isSystem, @ApiParam(value = "Widget Bundle alias", required = true) @RequestParam String bundleAlias) throws ThingsboardException { - try { - TenantId tenantId; - if (isSystem) { - tenantId = TenantId.SYS_TENANT_ID; - } else { - tenantId = getCurrentUser().getTenantId(); - } - return checkNotNull(widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(tenantId, bundleAlias)); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId; + if (isSystem) { + tenantId = TenantId.SYS_TENANT_ID; + } else { + tenantId = getCurrentUser().getTenantId(); } + return checkNotNull(widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(tenantId, bundleAlias)); } @ApiOperation(value = "Get all Widget types details for specified Bundle (getBundleWidgetTypes)", @@ -183,17 +166,13 @@ public class WidgetTypeController extends AutoCommitController { @RequestParam boolean isSystem, @ApiParam(value = "Widget Bundle alias", required = true) @RequestParam String bundleAlias) throws ThingsboardException { - try { - TenantId tenantId; - if (isSystem) { - tenantId = TenantId.SYS_TENANT_ID; - } else { - tenantId = getCurrentUser().getTenantId(); - } - return checkNotNull(widgetTypeService.findWidgetTypesDetailsByTenantIdAndBundleAlias(tenantId, bundleAlias)); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId; + if (isSystem) { + tenantId = TenantId.SYS_TENANT_ID; + } else { + tenantId = getCurrentUser().getTenantId(); } + return checkNotNull(widgetTypeService.findWidgetTypesDetailsByTenantIdAndBundleAlias(tenantId, bundleAlias)); } @ApiOperation(value = "Get Widget Type Info objects (getBundleWidgetTypesInfos)", @@ -206,17 +185,13 @@ public class WidgetTypeController extends AutoCommitController { @RequestParam boolean isSystem, @ApiParam(value = "Widget Bundle alias", required = true) @RequestParam String bundleAlias) throws ThingsboardException { - try { - TenantId tenantId; - if (isSystem) { - tenantId = TenantId.SYS_TENANT_ID; - } else { - tenantId = getCurrentUser().getTenantId(); - } - return checkNotNull(widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(tenantId, bundleAlias)); - } catch (Exception e) { - throw handleException(e); + TenantId tenantId; + if (isSystem) { + tenantId = TenantId.SYS_TENANT_ID; + } else { + tenantId = getCurrentUser().getTenantId(); } + return checkNotNull(widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(tenantId, bundleAlias)); } @ApiOperation(value = "Get Widget Type (getWidgetType)", @@ -231,20 +206,16 @@ public class WidgetTypeController extends AutoCommitController { @RequestParam String bundleAlias, @ApiParam(value = "Widget Type alias", required = true) @RequestParam String alias) throws ThingsboardException { - try { - TenantId tenantId; - if (isSystem) { - tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID); - } else { - tenantId = getCurrentUser().getTenantId(); - } - WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, bundleAlias, alias); - checkNotNull(widgetType); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType); - return widgetType; - } catch (Exception e) { - throw handleException(e); + TenantId tenantId; + if (isSystem) { + tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID); + } else { + tenantId = getCurrentUser().getTenantId(); } + WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, bundleAlias, alias); + checkNotNull(widgetType); + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType); + return widgetType; } } diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java index 23ec498d0e..23331d95f4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,12 +74,8 @@ public class WidgetsBundleController extends BaseController { @ApiParam(value = WIDGET_BUNDLE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException { checkParameter("widgetsBundleId", strWidgetsBundleId); - try { - WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); - return checkWidgetsBundleId(widgetsBundleId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); + return checkWidgetsBundleId(widgetsBundleId, Operation.READ); } @ApiOperation(value = "Create Or Update Widget Bundle (saveWidgetsBundle)", @@ -141,16 +137,12 @@ public class WidgetsBundleController extends BaseController { @RequestParam(required = false) String sortProperty, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink)); - } else { - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink)); - } - } catch (Exception e) { - throw handleException(e); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { + return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink)); + } else { + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink)); } } @@ -160,15 +152,11 @@ public class WidgetsBundleController extends BaseController { @RequestMapping(value = "/widgetsBundles", method = RequestMethod.GET) @ResponseBody public List getWidgetsBundles() throws ThingsboardException { - try { - if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - return checkNotNull(widgetsBundleService.findSystemWidgetsBundles(getTenantId())); - } else { - TenantId tenantId = getCurrentUser().getTenantId(); - return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenantId)); - } - } catch (Exception e) { - throw handleException(e); + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { + return checkNotNull(widgetsBundleService.findSystemWidgetsBundles(getTenantId())); + } else { + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenantId)); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 3a429add9b..200a6c71d2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; @@ -40,10 +41,11 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; -import org.thingsboard.server.service.telemetry.SessionEvent; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; +import org.thingsboard.server.service.ws.SessionEvent; +import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.WebSocketSessionType; import javax.websocket.RemoteEndpoint; import javax.websocket.SendHandler; @@ -59,20 +61,21 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; -import static org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService.NUMBER_OF_PING_ATTEMPTS; +import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @Service @TbCoreComponent @Slf4j -public class TbWebSocketHandler extends TextWebSocketHandler implements TelemetryWebSocketMsgEndpoint { +public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocketMsgEndpoint { - private static final ConcurrentMap internalSessionMap = new ConcurrentHashMap<>(); - private static final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>(); + private final ConcurrentMap internalSessionMap = new ConcurrentHashMap<>(); + private final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>(); - @Autowired - private TelemetryWebSocketService webSocketService; + @Autowired @Lazy + private WebSocketService webSocketService; @Autowired private TbTenantProfileCache tenantProfileCache; @@ -82,13 +85,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr @Value("${server.ws.ping_timeout:30000}") private long pingTimeout; - private ConcurrentMap blacklistedSessions = new ConcurrentHashMap<>(); - private ConcurrentMap perSessionUpdateLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap blacklistedSessions = new ConcurrentHashMap<>(); + private final ConcurrentMap perSessionUpdateLimits = new ConcurrentHashMap<>(); - private ConcurrentMap> tenantSessionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> customerSessionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> regularUserSessionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> publicUserSessionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantSessionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> customerSessionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> regularUserSessionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> publicUserSessionsMap = new ConcurrentHashMap<>(); @Override public void handleTextMessage(WebSocketSession session, TextMessage message) { @@ -133,7 +136,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } String internalSessionId = session.getId(); - TelemetryWebSocketSessionRef sessionRef = toRef(session); + WebSocketSessionRef sessionRef = toRef(session); String externalSessionId = sessionRef.getSessionId(); if (!checkLimits(session, sessionRef)) { @@ -142,7 +145,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, tenantProfileConfiguration != null && tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ? - tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500)); + tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500)); externalSessionMap.put(externalSessionId, internalSessionId); processInWebSocketService(sessionRef, SessionEvent.onEstablished()); @@ -182,7 +185,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) { + private void processInWebSocketService(WebSocketSessionRef sessionRef, SessionEvent event) { try { webSocketService.handleWebSocketSessionEvent(sessionRef, event); } catch (BeanCreationNotAllowedException e) { @@ -190,7 +193,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private TelemetryWebSocketSessionRef toRef(WebSocketSession session) throws IOException { + private WebSocketSessionRef toRef(WebSocketSession session) throws IOException { URI sessionUri = session.getUri(); String path = sessionUri.getPath(); path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length()); @@ -199,25 +202,30 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } String[] pathElements = path.split("/"); String serviceToken = pathElements[0]; - if (!"telemetry".equalsIgnoreCase(serviceToken)) { - throw new InvalidParameterException("Can't find plugin with specified token!"); - } else { - SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal(); - return new TelemetryWebSocketSessionRef(UUID.randomUUID().toString(), currentUser, session.getLocalAddress(), session.getRemoteAddress()); - } + WebSocketSessionType sessionType = WebSocketSessionType.forName(serviceToken) + .orElseThrow(() -> new InvalidParameterException("Can't find plugin with specified token!")); + + SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal(); + return WebSocketSessionRef.builder() + .sessionId(UUID.randomUUID().toString()) + .securityCtx(currentUser) + .localAddress(session.getLocalAddress()) + .remoteAddress(session.getRemoteAddress()) + .sessionType(sessionType) + .build(); } private class SessionMetaData implements SendHandler { private final WebSocketSession session; private final RemoteEndpoint.Async asyncRemote; - private final TelemetryWebSocketSessionRef sessionRef; + private final WebSocketSessionRef sessionRef; - private volatile boolean isSending = false; + private final AtomicBoolean isSending = new AtomicBoolean(false); private final Queue> msgQueue; private volatile long lastActivityTime; - SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { + SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { super(); this.session = session; Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class); @@ -259,7 +267,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } synchronized void sendMsg(TbWebSocketMsg msg) { - if (isSending) { + if (isSending.compareAndSet(false, true)) { + sendMsgInternal(msg); + } else { try { msgQueue.add(msg); } catch (RuntimeException e) { @@ -270,9 +280,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); } - } else { - isSending = true; - sendMsgInternal(msg); } } @@ -307,13 +314,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr if (msg != null) { sendMsgInternal(msg); } else { - isSending = false; + isSending.set(false); } } } @Override - public void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException { + public void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException { String externalId = sessionRef.getSessionId(); log.debug("[{}] Processing {}", externalId, msg); String internalId = externalSessionMap.get(externalId); @@ -349,7 +356,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } @Override - public void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException { + public void sendPing(WebSocketSessionRef sessionRef, long currentTime) throws IOException { String externalId = sessionRef.getSessionId(); String internalId = externalSessionMap.get(externalId); if (internalId != null) { @@ -365,7 +372,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } @Override - public void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus reason) throws IOException { + public void close(WebSocketSessionRef sessionRef, CloseStatus reason) throws IOException { String externalId = sessionRef.getSessionId(); log.debug("[{}] Processing close request", externalId); String internalId = externalSessionMap.get(externalId); @@ -381,7 +388,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private boolean checkLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) throws Exception { + private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws Exception { var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); if (tenantProfileConfiguration == null) { return true; @@ -448,7 +455,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr return true; } - private void cleanupLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) { + private void cleanupLimits(WebSocketSession session, WebSocketSessionRef sessionRef) { var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); if (tenantProfileConfiguration == null) return; @@ -483,9 +490,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } - private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) { + private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) { return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId())) .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); } -} \ No newline at end of file +} diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java index a48b9c5bf4..22c3207f12 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java index de2500d853..d529a1859d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java index 2fbc6c16a0..a641e20d58 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java index 54efca4da7..ee642bbaac 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java b/application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java rename to application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java index 43a7f8c56f..a82d4b6234 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java +++ b/application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java b/application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java rename to application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java index 3ede4e7e70..53fd323691 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java +++ b/application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java b/application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java rename to application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java index ae68ffdc3d..ffa0d0cb07 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java +++ b/application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java b/application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java rename to application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java index 7452d25a56..ad41e75b3d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java +++ b/application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java index 208fdead93..46e7205824 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java index 7cf2ca87f1..73cc5b0902 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index 30e934f386..5f22786907 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java b/application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java rename to application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java index 769c5ffe5e..ce79f852f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java +++ b/application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java b/application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java rename to application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java index b597a84e29..2d3d8dbf8f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java +++ b/application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java b/application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java rename to application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java index 8ee4bfa753..38f8a4a51f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java +++ b/application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.exception; +package org.thingsboard.server.exception; import org.springframework.http.ResponseEntity; diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java index 4338f4aa44..4daebe0b26 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java index 81d9a7d22a..c57ba35a14 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 9facea6dde..c3f1e44d12 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService; import org.thingsboard.server.service.install.DatabaseTsUpgradeService; import org.thingsboard.server.service.install.EntityDatabaseSchemaService; +import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.NoSqlKeyspaceService; import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.service.install.TsDatabaseSchemaService; @@ -34,6 +35,9 @@ import org.thingsboard.server.service.install.migrate.EntitiesMigrateService; import org.thingsboard.server.service.install.migrate.TsLatestMigrateService; import org.thingsboard.server.service.install.update.CacheCleanupService; import org.thingsboard.server.service.install.update.DataUpdateService; +import org.thingsboard.server.service.install.update.DefaultDataUpdateService; + +import static org.thingsboard.server.service.install.update.DefaultDataUpdateService.getEnv; @Service @Profile("install") @@ -88,6 +92,9 @@ public class ThingsboardInstallService { @Autowired(required = false) private TsLatestMigrateService latestMigrateService; + @Autowired + private InstallScripts installScripts; + public void performInstall() { try { if (isUpgrade) { @@ -233,12 +240,23 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.4.1"); dataUpdateService.updateData("3.4.1"); + case "3.4.2": + log.info("Upgrading ThingsBoard from version 3.4.2 to 3.4.3 ..."); + case "3.4.3": + log.info("Upgrading ThingsBoard from version 3.4.3 to 3.4.4 ..."); + case "3.4.4": + log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.4.4"); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); + if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) { + systemDataLoaderService.createDefaultNotificationConfigs(); + } else { + log.info("Skipping default notification configs creation"); + } + installScripts.loadSystemLwm2mResources(); break; - //TODO update CacheCleanupService on the next version upgrade - default: throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); @@ -277,8 +295,11 @@ public class ThingsboardInstallService { systemDataLoaderService.loadSystemWidgets(); systemDataLoaderService.createOAuth2Templates(); systemDataLoaderService.createQueues(); + systemDataLoaderService.createDefaultNotificationConfigs(); + // systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemRules(); + installScripts.loadSystemLwm2mResources(); if (loadDemo) { log.info("Loading demo data..."); @@ -297,3 +318,4 @@ public class ThingsboardInstallService { } } + diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index 7057f903be..d39e200337 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.CustomerId; @@ -86,9 +87,21 @@ public class EntityActionService { case ALARM_CLEAR: msgType = DataConstants.ALARM_CLEAR; break; + case ALARM_ASSIGN: + msgType = DataConstants.ALARM_ASSIGN; + break; + case ALARM_UNASSIGN: + msgType = DataConstants.ALARM_UNASSIGN; + break; case ALARM_DELETE: msgType = DataConstants.ALARM_DELETE; break; + case ADDED_COMMENT: + msgType = DataConstants.COMMENT_CREATED; + break; + case UPDATED_COMMENT: + msgType = DataConstants.COMMENT_UPDATED; + break; case ASSIGNED_FROM_TENANT: msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT; break; @@ -129,6 +142,13 @@ public class EntityActionService { if (user != null) { metaData.putValue("userId", user.getId().toString()); metaData.putValue("userName", user.getName()); + metaData.putValue("userEmail", user.getEmail()); + if (user.getFirstName() != null) { + metaData.putValue("userFirstName", user.getFirstName()); + } + if (user.getLastName() != null) { + metaData.putValue("userLastName", user.getLastName()); + } } if (customerId != null && !customerId.isNullUid()) { metaData.putValue("customerId", customerId.toString()); @@ -163,6 +183,9 @@ public class EntityActionService { String strEdgeName = extractParameter(String.class, 2, additionalInfo); metaData.putValue("unassignedEdgeId", strEdgeId); metaData.putValue("unassignedEdgeName", strEdgeName); + } else if (actionType == ActionType.ADDED_COMMENT || actionType == ActionType.UPDATED_COMMENT) { + AlarmComment comment = extractParameter(AlarmComment.class, 0, additionalInfo); + metaData.putValue("comment", json.writeValueAsString(comment)); } ObjectNode entityNode; if (entity != null) { @@ -170,6 +193,8 @@ public class EntityActionService { if (entityId.getEntityType() == EntityType.DASHBOARD) { entityNode.put("configuration", ""); } + metaData.putValue("entityName", entity.getName()); + metaData.putValue("entityType", entityId.getEntityType().toString()); } else { entityNode = json.createObjectNode(); if (actionType == ActionType.ATTRIBUTES_UPDATED) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java index 44e9c32864..ced982f5e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java index 00722425d6..6e93a1bab1 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index b8ca8b37c0..b4a625e7de 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,19 +17,19 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageRecordState; import org.thingsboard.server.common.data.ApiUsageState; -import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; @@ -52,6 +52,8 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -61,6 +63,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; @@ -77,8 +80,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -86,6 +87,7 @@ import java.util.stream.Collectors; @Slf4j @Service +@RequiredArgsConstructor public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; @@ -105,7 +107,9 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService private final ApiUsageStateService apiUsageStateService; private final TbTenantProfileCache tenantProfileCache; private final MailService mailService; + private final NotificationRuleProcessor notificationRuleProcessor; private final DbCallbackExecutorService dbExecutor; + private final MailExecutorService mailExecutor; @Lazy @Autowired @@ -126,27 +130,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService private final Lock updateLock = new ReentrantLock(); - private final ExecutorService mailExecutor; - - public DefaultTbApiUsageStateService(TbClusterService clusterService, - PartitionService partitionService, - TenantService tenantService, - TimeseriesService tsService, - ApiUsageStateService apiUsageStateService, - TbTenantProfileCache tenantProfileCache, - MailService mailService, - DbCallbackExecutorService dbExecutor) { - this.clusterService = clusterService; - this.partitionService = partitionService; - this.tenantService = tenantService; - this.tsService = tsService; - this.apiUsageStateService = apiUsageStateService; - this.tenantProfileCache = tenantProfileCache; - this.mailService = mailService; - this.mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail")); - this.dbExecutor = dbExecutor; - } - @PostConstruct public void init() { super.init(); @@ -356,30 +339,34 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { String email = tenantService.findTenantById(state.getTenantId()).getEmail(); - if (StringUtils.isNotEmpty(email)) { - result.forEach((apiFeature, stateValue) -> { + result.forEach((apiFeature, stateValue) -> { + ApiUsageRecordState recordState = createApiUsageRecordState((TenantApiUsageState) state, apiFeature, stateValue); + notificationRuleProcessor.process(ApiUsageLimitTrigger.builder() + .tenantId(state.getTenantId()) + .state(recordState) + .status(stateValue) + .build()); + if (StringUtils.isNotEmpty(email)) { mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage((TenantApiUsageState) state, apiFeature, stateValue)); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, recordState); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } }); - }); - } else { - log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); - } + } + }); } } - private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { + private ApiUsageRecordState createApiUsageRecordState(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { StateChecker checker = getStateChecker(stateValue); for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { long threshold = state.getProfileThreshold(apiUsageRecordKey); long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); long value = state.get(apiUsageRecordKey); if (checker.check(threshold, warnThreshold, value)) { - return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); + return new ApiUsageRecordState(apiFeature, apiUsageRecordKey, threshold, value); } } return null; @@ -391,7 +378,7 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService } else if (ApiUsageStateValue.WARNING.equals(stateValue)) { return (t, wt, v) -> v < t && v >= wt; } else { - return (t, wt, v) -> v >= t; + return (t, wt, v) -> t > 0 && v >= t; } } @@ -543,8 +530,5 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService @PreDestroy private void destroy() { super.stop(); - if (mailExecutor != null) { - mailExecutor.shutdownNow(); - } } } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 5f01d34877..9c5e9ae9b1 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0229d95c18..fcba410bbd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java similarity index 64% rename from application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java rename to application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java index 525386d884..e995a8bd1f 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.apiusage; +package org.thingsboard.server.service.apiusage.limits; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; @Service @RequiredArgsConstructor @@ -33,28 +31,22 @@ public class DefaultRateLimitService implements RateLimitService { private final TbTenantProfileCache tenantProfileCache; - private final Map> rateLimits = new ConcurrentHashMap<>(); + private final Map> rateLimits = new ConcurrentHashMap<>(); @Override - public boolean checkEntityExportLimit(TenantId tenantId) { - return checkLimit(tenantId, "entityExport", DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit); - } - - @Override - public boolean checkEntityImportLimit(TenantId tenantId) { - return checkLimit(tenantId, "entityImport", DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit); - } - - private boolean checkLimit(TenantId tenantId, String rateLimitsKey, Function rateLimitConfigExtractor) { + public boolean checkRateLimit(TenantId tenantId, LimitedApi api) { + if (tenantId.isSysTenantId()) { + return true; + } String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration() - .map(rateLimitConfigExtractor).orElse(null); + .map(api::getLimitConfig).orElse(null); - Map rateLimits = this.rateLimits.get(rateLimitsKey); + Map rateLimits = this.rateLimits.get(api); if (StringUtils.isEmpty(rateLimitConfig)) { if (rateLimits != null) { rateLimits.remove(tenantId); if (rateLimits.isEmpty()) { - this.rateLimits.remove(rateLimitsKey); + this.rateLimits.remove(api); } } return true; @@ -62,7 +54,7 @@ public class DefaultRateLimitService implements RateLimitService { if (rateLimits == null) { rateLimits = new ConcurrentHashMap<>(); - this.rateLimits.put(rateLimitsKey, rateLimits); + this.rateLimits.put(api, rateLimits); } TbRateLimits rateLimit = rateLimits.get(tenantId); if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java new file mode 100644 index 0000000000..f69ece6661 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage.limits; + +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; + +import java.util.function.Function; + +@RequiredArgsConstructor +public enum LimitedApi { + + ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit), + ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit), + NOTIFICATION_REQUEST(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit); + + private final Function configExtractor; + + public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration) { + return configExtractor.apply(profileConfiguration); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java similarity index 75% rename from application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java rename to application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java index d3d4244ca1..c984ec8fa5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.apiusage; +package org.thingsboard.server.service.apiusage.limits; import org.thingsboard.server.common.data.id.TenantId; public interface RateLimitService { - boolean checkEntityExportLimit(TenantId tenantId); - - boolean checkEntityImportLimit(TenantId tenantId); + boolean checkRateLimit(TenantId tenantId, LimitedApi api); } diff --git a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java index 5142311da9..feca584258 100644 --- a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 7a95bf78f9..732d8ce409 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java index 05dd8aa560..731009575d 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index a397a4d0e9..a0e6545652 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index 9f8d58b566..509e0fdbef 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index c88cbd8156..80b51f7ad5 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index 6d368fbed8..e0bfe75793 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,22 +41,22 @@ import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.AssetProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.UserEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.WidgetBundleEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.WidgetTypeEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.asset.AssetProfileEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.customer.CustomerEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.device.DeviceProfileEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java index f8c23ece1e..03e249ca26 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index 4adb73ebbb..f8321c39f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,24 +40,24 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings; import org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor; -import org.thingsboard.server.service.edge.rpc.processor.AdminSettingsEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.AssetProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.TelemetryEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.UserEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.WidgetBundleEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.WidgetTypeEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.settings.AdminSettingsEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.asset.AssetProfileEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.customer.CustomerEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.device.DeviceProfileEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.telemetry.TelemetryEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor; import org.thingsboard.server.service.edge.rpc.sync.EdgeRequestsService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.GrpcCallbackExecutorService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java index ee36c80454..91de140959 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java new file mode 100644 index 0000000000..9dd65eef60 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java @@ -0,0 +1,94 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.instructions; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.install.InstallScripts; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Service +@Slf4j +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") +@TbCoreComponent +public class DefaultEdgeInstallService implements EdgeInstallService { + + private static final String EDGE_DIR = "edge"; + + private static final String EDGE_INSTALL_INSTRUCTIONS_DIR = "install_instructions"; + + private final InstallScripts installScripts; + + @Value("${edges.rpc.port}") + private int rpcPort; + + @Value("${edges.rpc.ssl.enabled}") + private boolean sslEnabled; + + @Value("${app.version:unknown}") + private String appVersion; + + @Override + public EdgeInstallInstructions getDockerInstallInstructions(TenantId tenantId, Edge edge, HttpServletRequest request) { + String dockerInstallInstructions = readFile(resolveFile("docker", "instructions.md")); + String baseUrl = request.getServerName(); + if (baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { + String localhostWarning = readFile(resolveFile("docker", "localhost_warning.md")); + dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", localhostWarning); + dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", "!!!REPLACE_ME_TO_HOST_IP_ADDRESS!!!"); + } else { + dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", ""); + dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", baseUrl); + } + dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", appVersion + "EDGE"); + dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_KEY}", edge.getRoutingKey()); + dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_SECRET}", edge.getSecret()); + dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_RPC_PORT}", Integer.toString(rpcPort)); + dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_RPC_SSL_ENABLED}", Boolean.toString(sslEnabled)); + return new EdgeInstallInstructions(dockerInstallInstructions); + } + + private String readFile(Path file) { + try { + return new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + } catch (IOException e) { + log.warn("Failed to read file: {}", file, e); + throw new RuntimeException(e); + } + } + + private Path resolveFile(String subDir, String... subDirs) { + return getEdgeInstallInstructionsDir().resolve(Paths.get(subDir, subDirs)); + } + + private Path getEdgeInstallInstructionsDir() { + return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, EDGE_INSTALL_INSTRUCTIONS_DIR); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java new file mode 100644 index 0000000000..2a030fc4fa --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.instructions; + +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; +import org.thingsboard.server.common.data.id.TenantId; + +import javax.servlet.http.HttpServletRequest; + +public interface EdgeInstallService { + + EdgeInstallInstructions getDockerInstallInstructions(TenantId tenantId, Edge edge, HttpServletRequest request); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventStorageSettings.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventStorageSettings.java index 81c3ca644b..1830cfe9d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventStorageSettings.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventStorageSettings.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index fea89d6336..f695fa1d0e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 28cc32efa3..efa0fb4de7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -193,8 +193,10 @@ public final class EdgeGrpcSession implements Closeable { private void doSync(EdgeSyncCursor cursor) { if (cursor.hasNext()) { - log.info("[{}][{}] starting sync process, cursor current idx = {}", edge.getTenantId(), edge.getId(), cursor.getCurrentIdx()); - ListenableFuture uuidListenableFuture = startProcessingEdgeEvents(cursor.getNext()); + EdgeEventFetcher next = cursor.getNext(); + log.info("[{}][{}] starting sync process, cursor current idx = {}, class = {}", + edge.getTenantId(), edge.getId(), cursor.getCurrentIdx(), next.getClass().getSimpleName()); + ListenableFuture uuidListenableFuture = startProcessingEdgeEvents(next); Futures.addCallback(uuidListenableFuture, new FutureCallback<>() { @Override public void onSuccess(@Nullable UUID result) { @@ -421,6 +423,7 @@ public final class EdgeGrpcSession implements Closeable { stopCurrentSendDownlinkMsgsTask(null); } } catch (Exception e) { + log.warn("[{}] Failed to send downlink msgs. Error msg {}", this.sessionId, e.getMessage(), e); stopCurrentSendDownlinkMsgsTask(e); } }; @@ -455,7 +458,6 @@ public final class EdgeGrpcSession implements Closeable { case ASSIGNED_TO_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER: case CREDENTIALS_REQUEST: - case ENTITY_MERGE_REQUEST: case RPC_CALL: downlinkMsg = convertEntityEventToDownlink(edgeEvent); log.trace("[{}][{}] entity message processed [{}]", edgeEvent.getTenantId(), this.sessionId, downlinkMsg); @@ -554,27 +556,27 @@ public final class EdgeGrpcSession implements Closeable { try { if (uplinkMsg.getEntityDataCount() > 0) { for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) { - result.addAll(ctx.getTelemetryProcessor().processTelemetryFromEdge(edge.getTenantId(), entityData)); + result.addAll(ctx.getTelemetryProcessor().processTelemetryMsg(edge.getTenantId(), entityData)); } } if (uplinkMsg.getDeviceUpdateMsgCount() > 0) { for (DeviceUpdateMsg deviceUpdateMsg : uplinkMsg.getDeviceUpdateMsgList()) { - result.add(ctx.getDeviceProcessor().processDeviceFromEdge(edge.getTenantId(), edge, deviceUpdateMsg)); + result.add(ctx.getDeviceProcessor().processDeviceMsgFromEdge(edge.getTenantId(), edge, deviceUpdateMsg)); } } if (uplinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) { for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg : uplinkMsg.getDeviceCredentialsUpdateMsgList()) { - result.add(ctx.getDeviceProcessor().processDeviceCredentialsFromEdge(edge.getTenantId(), deviceCredentialsUpdateMsg)); + result.add(ctx.getDeviceProcessor().processDeviceCredentialsMsg(edge.getTenantId(), deviceCredentialsUpdateMsg)); } } if (uplinkMsg.getAlarmUpdateMsgCount() > 0) { for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) { - result.add(ctx.getAlarmProcessor().processAlarmFromEdge(edge.getTenantId(), alarmUpdateMsg)); + result.add(ctx.getAlarmProcessor().processAlarmMsg(edge.getTenantId(), alarmUpdateMsg)); } } if (uplinkMsg.getRelationUpdateMsgCount() > 0) { for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) { - result.add(ctx.getRelationProcessor().processRelationFromEdge(edge.getTenantId(), relationUpdateMsg)); + result.add(ctx.getRelationProcessor().processRelationMsg(edge.getTenantId(), relationUpdateMsg)); } } if (uplinkMsg.getRuleChainMetadataRequestMsgCount() > 0) { @@ -680,7 +682,7 @@ public final class EdgeGrpcSession implements Closeable { public void stopCurrentSendDownlinkMsgsTask(Exception e) { if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { if (e != null) { - log.warn(e.getMessage(), e); + log.debug(e.getMessage()); sessionState.getSendDownlinkMsgsFuture().setException(e); } else { sessionState.getSendDownlinkMsgsFuture().set(null); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java index 6c5515337e..d5315ae650 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java index 5a3d8dc0f8..59910c829f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index e9232584be..bf3e804f7e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.edge.rpc; +import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.edge.EdgeContextComponent; @@ -41,9 +42,9 @@ import java.util.NoSuchElementException; public class EdgeSyncCursor { - List fetchers = new LinkedList<>(); + private final List fetchers = new LinkedList<>(); - int currentIdx = 0; + private int currentIdx = 0; public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge, boolean fullSync) { if (fullSync) { @@ -53,8 +54,10 @@ public class EdgeSyncCursor { fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); fetchers.add(new AssetProfilesEdgeEventFetcher(ctx.getAssetProfileService())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); + Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId()); + fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId())); if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) { - fetchers.add(new CustomerEdgeEventFetcher()); + fetchers.add(new CustomerEdgeEventFetcher(edge.getCustomerId())); fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java index ee33dd687b..099811c45e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java index e8f70e56c4..9b4f649336 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java index f8cff82897..86ffc6dd5c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java index ec71217955..f5b921a32b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,10 @@ public class AssetProfileMsgConstructor { if (assetProfile.getImage() != null) { builder.setImage(ByteString.copyFrom(assetProfile.getImage().getBytes(StandardCharsets.UTF_8))); } + if (assetProfile.getDefaultEdgeRuleChainId() != null) { + builder.setDefaultRuleChainIdMSB(assetProfile.getDefaultEdgeRuleChainId().getId().getMostSignificantBits()) + .setDefaultRuleChainIdLSB(assetProfile.getDefaultEdgeRuleChainId().getId().getLeastSignificantBits()); + } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java index 38eb35782c..0465dd5a94 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java index c61c5eb260..edfdefb603 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java index 522bcd3a4e..78b785a5d8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public class DeviceMsgConstructor { @Autowired private DataDecodingEncodingService dataDecodingEncodingService; - public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device, String conflictName) { + public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) { DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(device.getId().getId().getMostSignificantBits()) @@ -66,8 +66,9 @@ public class DeviceMsgConstructor { builder.setFirmwareIdMSB(device.getFirmwareId().getId().getMostSignificantBits()) .setFirmwareIdLSB(device.getFirmwareId().getId().getLeastSignificantBits()); } - if (conflictName != null) { - builder.setConflictName(conflictName); + if (device.getSoftwareId() != null) { + builder.setSoftwareIdMSB(device.getSoftwareId().getId().getMostSignificantBits()) + .setSoftwareIdLSB(device.getSoftwareId().getId().getLeastSignificantBits()); } if (device.getDeviceData() != null) { builder.setDeviceDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(device.getDeviceData()))); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java index 7865adb292..d8e19ee854 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,18 @@ public class DeviceProfileMsgConstructor { builder.setFirmwareIdMSB(deviceProfile.getFirmwareId().getId().getMostSignificantBits()) .setFirmwareIdLSB(deviceProfile.getFirmwareId().getId().getLeastSignificantBits()); } + if (deviceProfile.getSoftwareId() != null) { + builder.setSoftwareIdMSB(deviceProfile.getSoftwareId().getId().getMostSignificantBits()) + .setSoftwareIdLSB(deviceProfile.getSoftwareId().getId().getLeastSignificantBits()); + } + if (deviceProfile.getDefaultEdgeRuleChainId() != null) { + builder.setDefaultRuleChainIdMSB(deviceProfile.getDefaultEdgeRuleChainId().getId().getMostSignificantBits()) + .setDefaultRuleChainIdLSB(deviceProfile.getDefaultEdgeRuleChainId().getId().getLeastSignificantBits()); + } + if (deviceProfile.getDefaultDashboardId() != null) { + builder.setDefaultDashboardIdMSB(deviceProfile.getDefaultDashboardId().getId().getMostSignificantBits()) + .setDefaultDashboardIdLSB(deviceProfile.getDefaultDashboardId().getId().getLeastSignificantBits()); + } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java index 5501217578..3416240b2c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java index e2d3735910..242a15ba56 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java index d5aa79700d..235199d75e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java index 35a1a6e0ac..253305a5ab 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.java index 85362ff70b..2644a5409b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java index c445dea37d..24786a4f7c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java index 7f5ae72511..8f91f5d19e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java index 625b555e24..fbd3b31688 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java index ef95577d0d..a574b8bb4b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java index 33ce6e613d..36e43b9579 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java index 8fb32926f5..28c1b1c458 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java index 014023cb60..51b7380e48 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java index 5fc686f86a..1c581267c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java index 3172ee9277..5e1310a5a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java index 9acb2ae9f5..6fe39942ad 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index b7c30fde64..cefeb4c46a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java index efade1b235..d2189b22a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java index ead87638fb..8aeff9954d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java index 5d7a5cb231..c44d90264a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java index 6791ba69a6..0049426f02 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java index 709c438739..05e7427121 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java index de88c2f05b..ace25dc7f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -33,6 +34,8 @@ import java.util.List; @AllArgsConstructor public class CustomerEdgeEventFetcher implements EdgeEventFetcher { + private final CustomerId customerId; + @Override public PageLink getPageLink(int pageSize) { return null; @@ -42,7 +45,7 @@ public class CustomerEdgeEventFetcher implements EdgeEventFetcher { public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { List result = new ArrayList<>(); result.add(EdgeUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(), - EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, edge.getCustomerId(), null)); + EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null)); // @voba - returns PageData object to be in sync with other fetchers return new PageData<>(result, 1, result.size(), false); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java index 2cc923d08a..4159cdc0f6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java index a6e7b3307c..81f508cd44 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java index ef4a5ac9ea..8c25f973f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java index fe896dbb79..90decaec26 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EdgeEventFetcher.java index f2a026f4c2..23d2a866fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java index 3bc4befdbe..5ec9c52f19 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java index ed5e039b62..327184e6a9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java index 14b8bac3f4..a2a25ada15 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java index a47dfa50a1..76e09d2e5f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java index 91f453d3de..64735b4925 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemWidgetsBundlesEdgeEventFetcher.java index 1aca3803d1..87cffcee9f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemWidgetsBundlesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemWidgetsBundlesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java index a496231f7a..933fcf2766 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java index 9983e4b0b2..5e136f664b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java deleted file mode 100644 index b5d256bfa8..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java +++ /dev/null @@ -1,195 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Component -@Slf4j -@TbCoreComponent -public class AlarmEdgeProcessor extends BaseEdgeProcessor { - - public ListenableFuture processAlarmFromEdge(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { - log.trace("[{}] onAlarmUpdate [{}]", tenantId, alarmUpdateMsg); - EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), - EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); - if (originatorId == null) { - log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); - return Futures.immediateFuture(null); - } - try { - Alarm existentAlarm = alarmService.findLatestByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType()).get(); - switch (alarmUpdateMsg.getMsgType()) { - case ENTITY_CREATED_RPC_MESSAGE: - case ENTITY_UPDATED_RPC_MESSAGE: - if (existentAlarm == null || existentAlarm.getStatus().isCleared()) { - existentAlarm = new Alarm(); - existentAlarm.setTenantId(tenantId); - existentAlarm.setType(alarmUpdateMsg.getName()); - existentAlarm.setOriginator(originatorId); - existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); - existentAlarm.setStartTs(alarmUpdateMsg.getStartTs()); - existentAlarm.setClearTs(alarmUpdateMsg.getClearTs()); - existentAlarm.setPropagate(alarmUpdateMsg.getPropagate()); - } - existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus())); - existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); - existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); - existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); - alarmService.createOrUpdateAlarm(existentAlarm); - break; - case ALARM_ACK_RPC_MESSAGE: - if (existentAlarm != null) { - alarmService.ackAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs()); - } - break; - case ALARM_CLEAR_RPC_MESSAGE: - if (existentAlarm != null) { - alarmService.clearAlarm(tenantId, existentAlarm.getId(), - JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs()); - } - break; - case ENTITY_DELETED_RPC_MESSAGE: - if (existentAlarm != null) { - alarmService.deleteAlarm(tenantId, existentAlarm.getId()); - } - break; - } - return Futures.immediateFuture(null); - } catch (Exception e) { - log.error("Failed to process alarm update msg [{}]", alarmUpdateMsg, e); - return Futures.immediateFailedFuture(new RuntimeException("Failed to process alarm update msg", e)); - } - } - - private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { - switch (entityType) { - case DEVICE: - return deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId(); - case ASSET: - return assetService.findAssetByTenantIdAndName(tenantId, entityName).getId(); - case ENTITY_VIEW: - return entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId(); - default: - return null; - } - } - - public DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent) { - AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - switch (edgeEvent.getAction()) { - case ADDED: - case UPDATED: - case ALARM_ACK: - case ALARM_CLEAR: - try { - Alarm alarm = alarmService.findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get(); - if (alarm != null) { - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edgeEvent.getTenantId(), msgType, alarm)) - .build(); - } - } catch (Exception e) { - log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e); - } - break; - case DELETED: - Alarm alarm = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), Alarm.class); - AlarmUpdateMsg alarmUpdateMsg = - alarmMsgConstructor.constructAlarmUpdatedMsg(edgeEvent.getTenantId(), msgType, alarm); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAlarmUpdateMsg(alarmUpdateMsg) - .build(); - break; - } - return downlinkMsg; - } - - public ListenableFuture processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { - EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); - AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - switch (actionType) { - case DELETED: - EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); - Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), Alarm.class); - return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); - default: - ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); - return Futures.transformAsync(alarmFuture, alarm -> { - if (alarm == null) { - return Futures.immediateFuture(null); - } - EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); - if (type == null) { - return Futures.immediateFuture(null); - } - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - List> futures = new ArrayList<>(); - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId relatedEdgeId : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, - relatedEdgeId, - EdgeEventType.ALARM, - EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), - alarmId, - null)); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - }, dbCallbackExecutorService); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 3b39520606..ac7c791338 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,10 +97,14 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; @Slf4j public abstract class BaseEdgeProcessor { + protected static final Lock deviceCreationLock = new ReentrantLock(); + protected static final int DEFAULT_PAGE_SIZE = 100; @Autowired @@ -306,6 +310,12 @@ public abstract class BaseEdgeProcessor { return futures; } + protected ListenableFuture handleUnsupportedMsgType(UpdateMsgType msgType) { + String errMsg = String.format("Unsupported msg type %s", msgType); + log.error(errMsg); + return Futures.immediateFailedFuture(new RuntimeException(errMsg)); + } + protected UpdateMsgType getUpdateMsgType(EdgeEventActionType actionType) { switch (actionType) { case UPDATED: @@ -462,4 +472,40 @@ public abstract class BaseEdgeProcessor { return null; } } + + protected UUID safeGetUUID(long mSB, long lSB) { + return mSB != 0 && lSB != 0 ? new UUID(mSB, lSB) : null; + } + + protected CustomerId safeGetCustomerId(long mSB, long lSB) { + CustomerId customerId = null; + UUID customerUUID = safeGetUUID(mSB, lSB); + if (customerUUID != null) { + customerId = new CustomerId(customerUUID); + } + return customerId; + } + + protected boolean isEntityExists(TenantId tenantId, EntityId entityId) { + switch (entityId.getEntityType()) { + case TENANT: + return tenantService.findTenantById(tenantId) != null; + case DEVICE: + return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; + case ASSET: + return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; + case ENTITY_VIEW: + return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; + case CUSTOMER: + return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; + case USER: + return userService.findUserById(tenantId, new UserId(entityId.getId())) != null; + case DASHBOARD: + return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; + case EDGE: + return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null; + default: + return false; + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java deleted file mode 100644 index 2998001a0a..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -@Component -@Slf4j -@TbCoreComponent -public class RelationEdgeProcessor extends BaseEdgeProcessor { - - public ListenableFuture processRelationFromEdge(TenantId tenantId, RelationUpdateMsg relationUpdateMsg) { - log.trace("[{}] onRelationUpdate [{}]", tenantId, relationUpdateMsg); - try { - EntityRelation entityRelation = new EntityRelation(); - - UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); - EntityId fromId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getFromEntityType()), fromUUID); - entityRelation.setFrom(fromId); - - UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); - EntityId toId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getToEntityType()), toUUID); - entityRelation.setTo(toId); - - entityRelation.setType(relationUpdateMsg.getType()); - if (relationUpdateMsg.hasTypeGroup()) { - entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup())); - } - entityRelation.setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.readTree(relationUpdateMsg.getAdditionalInfo())); - switch (relationUpdateMsg.getMsgType()) { - case ENTITY_CREATED_RPC_MESSAGE: - case ENTITY_UPDATED_RPC_MESSAGE: - if (isEntityExists(tenantId, entityRelation.getTo()) - && isEntityExists(tenantId, entityRelation.getFrom())) { - relationService.saveRelationAsync(tenantId, entityRelation); - } - break; - case ENTITY_DELETED_RPC_MESSAGE: - relationService.deleteRelation(tenantId, entityRelation); - break; - case UNRECOGNIZED: - log.error("Unsupported msg type"); - } - return Futures.immediateFuture(null); - } catch (Exception e) { - log.error("Failed to process relation update msg [{}]", relationUpdateMsg, e); - return Futures.immediateFailedFuture(new RuntimeException("Failed to process relation update msg", e)); - } - } - - - private boolean isEntityExists(TenantId tenantId, EntityId entityId) throws ThingsboardException { - switch (entityId.getEntityType()) { - case DEVICE: - return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; - case ASSET: - return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; - case ENTITY_VIEW: - return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; - case CUSTOMER: - return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; - case USER: - return userService.findUserById(tenantId, new UserId(entityId.getId())) != null; - case DASHBOARD: - return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; - default: - throw new ThingsboardException("Unsupported entity type " + entityId.getEntityType(), ThingsboardErrorCode.INVALID_ARGUMENTS); - } - } - - public DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent) { - EntityRelation entityRelation = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), EntityRelation.class); - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - RelationUpdateMsg relationUpdateMsg = relationMsgConstructor.constructRelationUpdatedMsg(msgType, entityRelation); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addRelationUpdateMsg(relationUpdateMsg) - .build(); - } - - public ListenableFuture processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { - EntityRelation relation = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); - if (relation.getFrom().getEntityType().equals(EntityType.EDGE) || - relation.getTo().getEntityType().equals(EntityType.EDGE)) { - return Futures.immediateFuture(null); - } - - Set uniqueEdgeIds = new HashSet<>(); - uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); - uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); - if (uniqueEdgeIds.isEmpty()) { - return Futures.immediateFuture(null); - } - List> futures = new ArrayList<>(); - for (EdgeId edgeId : uniqueEdgeIds) { - futures.add(saveEdgeEvent(tenantId, - edgeId, - EdgeEventType.RELATION, - EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), - null, - JacksonUtil.OBJECT_MAPPER.valueToTree(relation))); - } - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java new file mode 100644 index 0000000000..189d69e68d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java @@ -0,0 +1,102 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.alarm; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +@Slf4j +@TbCoreComponent +public class AlarmEdgeProcessor extends BaseAlarmProcessor { + + public DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent) { + AlarmUpdateMsg alarmUpdateMsg = + convertAlarmEventToAlarmMsg(edgeEvent.getTenantId(), edgeEvent.getEntityId(), edgeEvent.getAction(), edgeEvent.getBody()); + if (alarmUpdateMsg != null) { + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addAlarmUpdateMsg(alarmUpdateMsg) + .build(); + } + return null; + } + + public ListenableFuture processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); + AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + switch (actionType) { + case DELETED: + EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); + Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), Alarm.class); + return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); + default: + ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); + return Futures.transformAsync(alarmFuture, alarm -> { + if (alarm == null) { + return Futures.immediateFuture(null); + } + EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); + if (type == null) { + return Futures.immediateFuture(null); + } + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId relatedEdgeId : pageData.getData()) { + futures.add(saveEdgeEvent(tenantId, + relatedEdgeId, + EdgeEventType.ALARM, + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), + alarmId, + null)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + }, dbCallbackExecutorService); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java new file mode 100644 index 0000000000..be64f475a2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -0,0 +1,137 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.alarm; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +import java.util.UUID; + +@Slf4j +public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { + + public ListenableFuture processAlarmMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { + log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg); + EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), + EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); + AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB())); + if (originatorId == null) { + log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); + return Futures.immediateFuture(null); + } + try { + switch (alarmUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE: + case ENTITY_UPDATED_RPC_MESSAGE: + Alarm alarm = new Alarm(); + alarm.setId(alarmId); + alarm.setTenantId(tenantId); + alarm.setType(alarmUpdateMsg.getName()); + alarm.setOriginator(originatorId); + alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); + alarm.setStartTs(alarmUpdateMsg.getStartTs()); + var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus()); + alarm.setClearTs(alarmUpdateMsg.getClearTs()); + alarm.setPropagate(alarmUpdateMsg.getPropagate()); + alarm.setCleared(alarmStatus.isCleared()); + alarm.setAcknowledged(alarmStatus.isAck()); + alarm.setAckTs(alarmUpdateMsg.getAckTs()); + alarm.setEndTs(alarmUpdateMsg.getEndTs()); + alarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); + if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(alarmUpdateMsg.getMsgType())) { + alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId)); + } else { + alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); + } + return Futures.immediateFuture(null); + case ALARM_ACK_RPC_MESSAGE: + Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId); + if (alarmToAck != null) { + alarmService.acknowledgeAlarm(tenantId, alarmId, alarmUpdateMsg.getAckTs()); + } + return Futures.immediateFuture(null); + case ALARM_CLEAR_RPC_MESSAGE: + Alarm alarmToClear = alarmService.findAlarmById(tenantId, alarmId); + if (alarmToClear != null) { + alarmService.clearAlarm(tenantId, alarmId, alarmUpdateMsg.getClearTs(), + JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); + } + return Futures.immediateFuture(null); + case ENTITY_DELETED_RPC_MESSAGE: + Alarm alarmToDelete = alarmService.findAlarmById(tenantId, alarmId); + if (alarmToDelete != null) { + alarmService.delAlarm(tenantId, alarmId); + } + return Futures.immediateFuture(null); + case UNRECOGNIZED: + default: + return handleUnsupportedMsgType(alarmUpdateMsg.getMsgType()); + } + } catch (Exception e) { + log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e); + return Futures.immediateFailedFuture(e); + } + } + + private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { + switch (entityType) { + case DEVICE: + return deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId(); + case ASSET: + return assetService.findAssetByTenantIdAndName(tenantId, entityName).getId(); + case ENTITY_VIEW: + return entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId(); + default: + return null; + } + } + + public AlarmUpdateMsg convertAlarmEventToAlarmMsg(TenantId tenantId, UUID entityId, EdgeEventActionType actionType, JsonNode body) { + AlarmId alarmId = new AlarmId(entityId); + UpdateMsgType msgType = getUpdateMsgType(actionType); + switch (actionType) { + case ADDED: + case UPDATED: + case ALARM_ACK: + case ALARM_CLEAR: + Alarm alarm = alarmService.findAlarmById(tenantId, alarmId); + if (alarm != null) { + return alarmMsgConstructor.constructAlarmUpdatedMsg(tenantId, msgType, alarm); + } + break; + case DELETED: + Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.convertValue(body, Alarm.class); + return alarmMsgConstructor.constructAlarmUpdatedMsg(tenantId, msgType, deletedAlarm); + } + return null; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java index 8f03523034..b86b8f3a26 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.asset; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -30,6 +30,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java index ce17c050b3..51c42e5764 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.asset; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java index 7d720fa671..f553d6d5a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.customer; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -36,6 +36,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.ArrayList; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java index 283df6954e..9651609bd3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.dashboard; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java new file mode 100644 index 0000000000..85eee6b2f2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java @@ -0,0 +1,134 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.device; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +import java.util.Optional; +import java.util.UUID; + +@Slf4j +public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { + + @Autowired + protected DataDecodingEncodingService dataDecodingEncodingService; + + protected Pair saveOrUpdateDevice(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg, CustomerId customerId) { + boolean created = false; + boolean deviceNameUpdated = false; + deviceCreationLock.lock(); + try { + Device device = deviceService.findDeviceById(tenantId, deviceId); + String deviceName = deviceUpdateMsg.getName(); + if (device == null) { + created = true; + device = new Device(); + device.setTenantId(tenantId); + device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); + Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); + if (deviceByName != null) { + deviceName = deviceName + "_" + StringUtils.randomAlphabetic(15); + log.warn("Device with name {} already exists. Renaming device name to {}", + deviceUpdateMsg.getName(), deviceName); + deviceNameUpdated = true; + } + } + device.setName(deviceName); + device.setType(deviceUpdateMsg.getType()); + device.setLabel(deviceUpdateMsg.hasLabel() ? deviceUpdateMsg.getLabel() : null); + device.setAdditionalInfo(deviceUpdateMsg.hasAdditionalInfo() + ? JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo()) : null); + + UUID deviceProfileUUID = safeGetUUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB()); + device.setDeviceProfileId(deviceProfileUUID != null ? new DeviceProfileId(deviceProfileUUID) : null); + + device.setCustomerId(customerId); + + Optional deviceDataOpt = + dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); + device.setDeviceData(deviceDataOpt.orElse(null)); + + UUID firmwareUUID = safeGetUUID(deviceUpdateMsg.getFirmwareIdMSB(), deviceUpdateMsg.getFirmwareIdLSB()); + device.setFirmwareId(firmwareUUID != null ? new OtaPackageId(firmwareUUID) : null); + + UUID softwareUUID = safeGetUUID(deviceUpdateMsg.getSoftwareIdMSB(), deviceUpdateMsg.getSoftwareIdLSB()); + device.setSoftwareId(softwareUUID != null ? new OtaPackageId(softwareUUID) : null); + deviceValidator.validate(device, Device::getTenantId); + if (created) { + device.setId(deviceId); + } + Device savedDevice = deviceService.saveDevice(device, false); + if (created) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20)); + deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); + } + tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); + } finally { + deviceCreationLock.unlock(); + } + return Pair.of(created, deviceNameUpdated); + } + + public ListenableFuture processDeviceCredentialsMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { + log.debug("[{}] Executing processDeviceCredentialsMsg, deviceCredentialsUpdateMsg [{}]", tenantId, deviceCredentialsUpdateMsg); + DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB())); + ListenableFuture deviceFuture = deviceService.findDeviceByIdAsync(tenantId, deviceId); + return Futures.transform(deviceFuture, device -> { + if (device != null) { + log.debug("Updating device credentials for device [{}]. New device credentials Id [{}], value [{}]", + device.getName(), deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentialsUpdateMsg.getCredentialsValue()); + try { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); + deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); + deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.hasCredentialsValue() + ? deviceCredentialsUpdateMsg.getCredentialsValue() : null); + deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); + } catch (Exception e) { + log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", + device.getName(), deviceCredentialsUpdateMsg, e); + throw new RuntimeException(e); + } + } else { + log.warn("Can't find device by id [{}], deviceCredentialsUpdateMsg [{}]", deviceId, deviceCredentialsUpdateMsg); + } + return null; + }, dbCallbackExecutorService); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java similarity index 50% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 81e7970611..e26499aacc 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.device; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; @@ -30,24 +29,19 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -63,220 +57,57 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; -import java.util.Optional; import java.util.UUID; -import java.util.concurrent.locks.ReentrantLock; @Component @Slf4j @TbCoreComponent -public class DeviceEdgeProcessor extends BaseEdgeProcessor { +public class DeviceEdgeProcessor extends BaseDeviceProcessor { - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - - private static final ReentrantLock deviceCreationLock = new ReentrantLock(); - - public ListenableFuture processDeviceFromEdge(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { - log.trace("[{}] onDeviceUpdate [{}] from edge [{}]", tenantId, deviceUpdateMsg, edge.getName()); - switch (deviceUpdateMsg.getMsgType()) { - case ENTITY_CREATED_RPC_MESSAGE: - String deviceName = deviceUpdateMsg.getName(); - Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); - if (device != null) { - boolean deviceAlreadyExistsForThisEdge = isDeviceAlreadyExistsOnCloudForThisEdge(tenantId, edge, device); - if (deviceAlreadyExistsForThisEdge) { - log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + - "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); - return updateDevice(tenantId, edge, deviceUpdateMsg); - } else { - log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." + - "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg); - String newDeviceName = deviceUpdateMsg.getName() + "_" + StringUtils.randomAlphabetic(15); - Device newDevice; - try { - newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); - } catch (DataValidationException e) { - log.error("[{}] Device update msg can't be processed due to data validation [{}]", tenantId, deviceUpdateMsg, e); - return Futures.immediateFuture(null); - } - ObjectNode body = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - body.put("conflictName", deviceName); - ListenableFuture input = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); - return Futures.transformAsync(input, unused -> - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null), - dbCallbackExecutorService); - } - } else { - log.info("[{}] Creating new device on the cloud [{}]", tenantId, deviceUpdateMsg); - try { - device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); - } catch (DataValidationException e) { - log.error("[{}] Device update msg can't be processed due to data validation [{}]", tenantId, deviceUpdateMsg, e); - return Futures.immediateFuture(null); - } - return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null); - } - case ENTITY_UPDATED_RPC_MESSAGE: - return updateDevice(tenantId, edge, deviceUpdateMsg); - case ENTITY_DELETED_RPC_MESSAGE: - DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); - Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId); - if (deviceToDelete != null) { - deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); - } - return Futures.immediateFuture(null); - case UNRECOGNIZED: - default: - log.error("Unsupported msg type {}", deviceUpdateMsg.getMsgType()); - return Futures.immediateFailedFuture(new RuntimeException("Unsupported msg type " + deviceUpdateMsg.getMsgType())); - } - } - - private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - if (pageData.getData().contains(edge.getId())) { - return true; - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - return false; - } - - public ListenableFuture processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { - log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg); - DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB())); - ListenableFuture deviceFuture = deviceService.findDeviceByIdAsync(tenantId, deviceId); - return Futures.transform(deviceFuture, device -> { - if (device != null) { - log.debug("Updating device credentials for device [{}]. New device credentials Id [{}], value [{}]", - device.getName(), deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentialsUpdateMsg.getCredentialsValue()); - try { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); - deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); - deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); - if (deviceCredentialsUpdateMsg.hasCredentialsValue()) { - deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.getCredentialsValue()); - } - deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); - } catch (Exception e) { - log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", device.getName(), deviceCredentialsUpdateMsg, e); - throw new RuntimeException(e); - } - } - return null; - }, dbCallbackExecutorService); - } - - - private ListenableFuture updateDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { + public ListenableFuture processDeviceMsgFromEdge(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { + log.trace("[{}] executing processDeviceMsgFromEdge [{}] from edge [{}]", tenantId, deviceUpdateMsg, edge.getName()); DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); - Device device = deviceService.findDeviceById(tenantId, deviceId); - if (device != null) { - device.setName(deviceUpdateMsg.getName()); - device.setType(deviceUpdateMsg.getType()); - if (deviceUpdateMsg.hasLabel()) { - device.setLabel(deviceUpdateMsg.getLabel()); - } - if (deviceUpdateMsg.hasAdditionalInfo()) { - device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); - } - if (deviceUpdateMsg.hasDeviceProfileIdMSB() && deviceUpdateMsg.hasDeviceProfileIdLSB()) { - DeviceProfileId deviceProfileId = new DeviceProfileId( - new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(), - deviceUpdateMsg.getDeviceProfileIdLSB())); - device.setDeviceProfileId(deviceProfileId); - } - device.setCustomerId(getCustomerId(deviceUpdateMsg)); - Optional deviceDataOpt = - dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); - if (deviceDataOpt.isPresent()) { - device.setDeviceData(deviceDataOpt.get()); - } - Device savedDevice = deviceService.saveDevice(device); - tbClusterService.onDeviceUpdated(savedDevice, device, false); - return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); - } else { - String errMsg = String.format("[%s] can't find device [%s], edge [%s]", tenantId, deviceUpdateMsg, edge.getId()); - log.warn(errMsg); - return Futures.immediateFailedFuture(new RuntimeException(errMsg)); - } - } - - private Device createDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg, String deviceName) { - Device device; - deviceCreationLock.lock(); try { - log.debug("[{}] Creating device entity [{}] from edge [{}]", tenantId, deviceUpdateMsg, edge.getName()); - DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); - device = deviceService.findDeviceById(tenantId, deviceId); - boolean created = false; - if (device == null) { - device = new Device(); - device.setTenantId(tenantId); - device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); - created = true; - } - device.setName(deviceName); - device.setType(deviceUpdateMsg.getType()); - if (deviceUpdateMsg.hasLabel()) { - device.setLabel(deviceUpdateMsg.getLabel()); - } - if (deviceUpdateMsg.hasAdditionalInfo()) { - device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); - } - if (deviceUpdateMsg.hasDeviceProfileIdMSB() && deviceUpdateMsg.hasDeviceProfileIdLSB()) { - DeviceProfileId deviceProfileId = new DeviceProfileId( - new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(), - deviceUpdateMsg.getDeviceProfileIdLSB())); - device.setDeviceProfileId(deviceProfileId); - } - device.setCustomerId(getCustomerId(deviceUpdateMsg)); - Optional deviceDataOpt = - dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); - if (deviceDataOpt.isPresent()) { - device.setDeviceData(deviceDataOpt.get()); + switch (deviceUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE: + case ENTITY_UPDATED_RPC_MESSAGE: + saveOrUpdateDevice(tenantId, deviceId, deviceUpdateMsg, edge); + return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); + case ENTITY_DELETED_RPC_MESSAGE: + Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId); + if (deviceToDelete != null) { + deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); + } + return Futures.immediateFuture(null); + case UNRECOGNIZED: + default: + return handleUnsupportedMsgType(deviceUpdateMsg.getMsgType()); } - if (created) { - deviceValidator.validate(device, Device::getTenantId); - device.setId(deviceId); + } catch (DataValidationException e) { + if (e.getMessage().contains("limit reached")) { + log.warn("[{}] Number of allowed devices violated {}", tenantId, deviceUpdateMsg, e); + return Futures.immediateFuture(null); } else { - deviceValidator.validate(device, Device::getTenantId); - } - Device savedDevice = deviceService.saveDevice(device, false); - tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); - if (created) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); - deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20)); - deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); + return Futures.immediateFailedFuture(e); } - createRelationFromEdge(tenantId, edge.getId(), device.getId()); - pushDeviceCreatedEventToRuleEngine(tenantId, edge, device); - deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); - } finally { - deviceCreationLock.unlock(); } - return device; } - private CustomerId getCustomerId(DeviceUpdateMsg deviceUpdateMsg) { - if (deviceUpdateMsg.hasCustomerIdMSB() && deviceUpdateMsg.hasCustomerIdLSB()) { - return new CustomerId(new UUID(deviceUpdateMsg.getCustomerIdMSB(), deviceUpdateMsg.getCustomerIdLSB())); - } else { - return null; + private void saveOrUpdateDevice(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg, Edge edge) { + CustomerId customerId = safeGetCustomerId(deviceUpdateMsg.getCustomerIdMSB(), deviceUpdateMsg.getCustomerIdLSB()); + Pair resultPair = super.saveOrUpdateDevice(tenantId, deviceId, deviceUpdateMsg, customerId); + Boolean created = resultPair.getFirst(); + if (created) { + createRelationFromEdge(tenantId, edge.getId(), deviceId); + pushDeviceCreatedEventToRuleEngine(tenantId, edge, deviceId); + deviceService.assignDeviceToEdge(tenantId, deviceId, edge.getId()); + } + Boolean deviceNameUpdated = resultPair.getSecond(); + if (deviceNameUpdated) { + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.UPDATED, deviceId, null); } } @@ -289,9 +120,9 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { relationService.saveRelation(tenantId, relation); } - private void pushDeviceCreatedEventToRuleEngine(TenantId tenantId, Edge edge, Device device) { + private void pushDeviceCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DeviceId deviceId) { try { - DeviceId deviceId = device.getId(); + Device device = deviceService.findDeviceById(tenantId, deviceId); ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, device.getCustomerId(), getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); @@ -307,7 +138,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { } }); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, DataConstants.ENTITY_CREATED, e); } } @@ -420,7 +251,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { if (device != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DeviceUpdateMsg deviceUpdateMsg = - deviceMsgConstructor.constructDeviceUpdatedMsg(msgType, device, null); + deviceMsgConstructor.constructDeviceUpdatedMsg(msgType, device); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceUpdateMsg(deviceUpdateMsg); @@ -455,14 +286,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { return convertRpcCallEventToDownlink(edgeEvent); case CREDENTIALS_REQUEST: return convertCredentialsRequestEventToDownlink(edgeEvent); - case ENTITY_MERGE_REQUEST: - return convertEntityMergeRequestEventToDownlink(edgeEvent); } return downlinkMsg; } private DownlinkMsg convertRpcCallEventToDownlink(EdgeEvent edgeEvent) { - log.trace("Executing convertRpcCallEventToDownlink, edgeEvent [{}]", edgeEvent); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceRpcCallMsg(deviceMsgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody())) @@ -481,21 +309,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { return builder.build(); } - public DownlinkMsg convertEntityMergeRequestEventToDownlink(EdgeEvent edgeEvent) { - DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); - Device device = deviceService.findDeviceById(edgeEvent.getTenantId(), deviceId); - String conflictName = null; - if(edgeEvent.getBody() != null) { - conflictName = edgeEvent.getBody().get("conflictName").asText(); - } - DeviceUpdateMsg deviceUpdateMsg = deviceMsgConstructor - .constructDeviceUpdatedMsg(UpdateMsgType.ENTITY_MERGE_RPC_MESSAGE, device, conflictName); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceUpdateMsg(deviceUpdateMsg) - .build(); - } - public ListenableFuture processDeviceNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { return processEntityNotification(tenantId, edgeNotificationMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java index d5d36ee640..5ddfecfdb1 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.device; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java index d4f6998926..123209afc1 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.edge; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -35,6 +35,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.ArrayList; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java index b89ce15a5d..29965fcc69 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.entityview; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java index 37ae5142af..8206e0f1b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.ota; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java index 6a7e66a58a..cbacaf9276 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.queue; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java new file mode 100644 index 0000000000..df683bbbe2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.relation; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +import java.util.UUID; + +@Slf4j +public abstract class BaseRelationProcessor extends BaseEdgeProcessor { + + public ListenableFuture processRelationMsg(TenantId tenantId, RelationUpdateMsg relationUpdateMsg) { + log.trace("[{}] processRelationFromEdge [{}]", tenantId, relationUpdateMsg); + try { + EntityRelation entityRelation = new EntityRelation(); + + UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); + EntityId fromId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getFromEntityType()), fromUUID); + entityRelation.setFrom(fromId); + + UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); + EntityId toId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getToEntityType()), toUUID); + entityRelation.setTo(toId); + + entityRelation.setType(relationUpdateMsg.getType()); + entityRelation.setTypeGroup(relationUpdateMsg.hasTypeGroup() + ? RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup()) : RelationTypeGroup.COMMON); + entityRelation.setAdditionalInfo(JacksonUtil.toJsonNode(relationUpdateMsg.getAdditionalInfo())); + switch (relationUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE: + case ENTITY_UPDATED_RPC_MESSAGE: + if (isEntityExists(tenantId, entityRelation.getTo()) + && isEntityExists(tenantId, entityRelation.getFrom())) { + return Futures.transform(relationService.saveRelationAsync(tenantId, entityRelation), + (result) -> null, dbCallbackExecutorService); + } else { + log.warn("Skipping relating update msg because from/to entity doesn't exists on edge, {}", relationUpdateMsg); + return Futures.immediateFuture(null); + } + case ENTITY_DELETED_RPC_MESSAGE: + return Futures.transform(relationService.deleteRelationAsync(tenantId, entityRelation), + (result) -> null, dbCallbackExecutorService); + case UNRECOGNIZED: + default: + return handleUnsupportedMsgType(relationUpdateMsg.getMsgType()); + } + } catch (Exception e) { + log.error("[{}] Failed to process relation update msg [{}]", tenantId, relationUpdateMsg, e); + return Futures.immediateFailedFuture(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java new file mode 100644 index 0000000000..48192a3973 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.relation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Component +@Slf4j +@TbCoreComponent +public class RelationEdgeProcessor extends BaseRelationProcessor { + + public DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent) { + EntityRelation entityRelation = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), EntityRelation.class); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + RelationUpdateMsg relationUpdateMsg = relationMsgConstructor.constructRelationUpdatedMsg(msgType, entityRelation); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addRelationUpdateMsg(relationUpdateMsg) + .build(); + } + + public ListenableFuture processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { + EntityRelation relation = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); + if (relation.getFrom().getEntityType().equals(EntityType.EDGE) || + relation.getTo().getEntityType().equals(EntityType.EDGE)) { + return Futures.immediateFuture(null); + } + + Set uniqueEdgeIds = new HashSet<>(); + uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); + uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); + if (uniqueEdgeIds.isEmpty()) { + return Futures.immediateFuture(null); + } + List> futures = new ArrayList<>(); + for (EdgeId edgeId : uniqueEdgeIds) { + futures.add(saveEdgeEvent(tenantId, + edgeId, + EdgeEventType.RELATION, + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), + null, + JacksonUtil.OBJECT_MAPPER.valueToTree(relation))); + } + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java similarity index 96% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index 22d8005472..69d4f7ef64 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.rule; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -31,6 +31,7 @@ import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java index c3d3df4492..6bd21e0796 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.settings; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java similarity index 68% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 481ee7762e..fece86b92a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,31 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.telemetry; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; -import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -59,24 +59,22 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; +import java.util.UUID; -@Component @Slf4j -@TbCoreComponent -public class TelemetryEdgeProcessor extends BaseEdgeProcessor { +public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { private final Gson gson = new Gson(); @@ -87,46 +85,52 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { tbCoreMsgProducer = producerProvider.getTbCoreMsgProducer(); } - public List> processTelemetryFromEdge(TenantId tenantId, EntityDataProto entityData) { - log.trace("[{}] processTelemetryFromEdge [{}]", tenantId, entityData); + abstract protected String getMsgSourceKey(); + + public List> processTelemetryMsg(TenantId tenantId, EntityDataProto entityData) { + log.trace("[{}] processTelemetryMsg [{}]", tenantId, entityData); List> result = new ArrayList<>(); EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB()); - if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) { - Pair pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId); - TbMsgMetaData metaData = pair.getKey(); - CustomerId customerId = pair.getValue(); - metaData.putValue(DataConstants.MSG_SOURCE_KEY, DataConstants.EDGE_MSG_SOURCE); - if (entityData.hasPostAttributesMsg()) { - result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); - } - if (entityData.hasAttributesUpdatedMsg()) { - metaData.putValue("scope", entityData.getPostAttributeScope()); - result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); - } - if (entityData.hasPostTelemetryMsg()) { - result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); - } - if (EntityType.DEVICE.equals(entityId.getEntityType())) { - DeviceId deviceId = new DeviceId(entityId.getId()); + if (entityId != null && isEntityExists(tenantId, entityId)) { + if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg())) { + Pair pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId); + TbMsgMetaData metaData = pair.getKey(); + CustomerId customerId = pair.getValue(); + metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey()); + if (entityData.hasPostAttributesMsg()) { + result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); + } + if (entityData.hasAttributesUpdatedMsg()) { + metaData.putValue("scope", entityData.getPostAttributeScope()); + result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); + } + if (entityData.hasPostTelemetryMsg()) { + result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); + } + if (EntityType.DEVICE.equals(entityId.getEntityType())) { + DeviceId deviceId = new DeviceId(entityId.getId()); - long currentTs = System.currentTimeMillis(); + long currentTs = System.currentTimeMillis(); - TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) - .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) - .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) - .setLastActivityTime(currentTs).build(); + TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastActivityTime(currentTs).build(); - log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs); + log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs); - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); - tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), - TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); + tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), + TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null); + } } - } - if (entityData.hasAttributeDeleteMsg()) { - result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType())); + if (entityData.hasAttributeDeleteMsg()) { + result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType())); + } + } else { + log.warn("Skipping telemetry update msg because entity doesn't exists on edge, {}", entityData); } return result; } @@ -280,69 +284,69 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { private ListenableFuture processAttributeDeleteMsg(TenantId tenantId, EntityId entityId, AttributeDeleteMsg attributeDeleteMsg, String entityType) { - SettableFuture futureToSet = SettableFuture.create(); + String scope = attributeDeleteMsg.getScope(); - List attributeNames = attributeDeleteMsg.getAttributeNamesList(); - attributesService.removeAll(tenantId, entityId, scope, attributeNames); - if (EntityType.DEVICE.name().equals(entityType)) { - tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( - tenantId, (DeviceId) entityId, scope, attributeNames), new TbQueueCallback() { - @Override - public void onSuccess(TbQueueMsgMetadata metadata) { - futureToSet.set(null); - } + List attributeKeys = attributeDeleteMsg.getAttributeNamesList(); + ListenableFuture> removeAllFuture = attributesService.removeAll(tenantId, entityId, scope, attributeKeys); + return Futures.transformAsync(removeAllFuture, removeAttributes -> { + if (EntityType.DEVICE.name().equals(entityType)) { + SettableFuture futureToSet = SettableFuture.create(); + tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( + tenantId, (DeviceId) entityId, scope, attributeKeys), new TbQueueCallback() { + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + futureToSet.set(null); + } - @Override - public void onFailure(Throwable t) { - log.error("Can't process attribute delete msg [{}]", attributeDeleteMsg, t); - futureToSet.setException(t); - } - }); - } - return futureToSet; + @Override + public void onFailure(Throwable t) { + log.error("Can't process attribute delete msg [{}]", attributeDeleteMsg, t); + futureToSet.setException(t); + } + }); + return futureToSet; + } else { + return Futures.immediateFuture(null); + } + }, dbCallbackExecutorService); } - public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { + public EntityDataProto convertTelemetryEventToEntityDataProto(EntityType entityType, + UUID entityUUID, + EdgeEventActionType actionType, + JsonNode body) throws JsonProcessingException { EntityId entityId; - switch (edgeEvent.getType()) { + switch (entityType) { case DEVICE: - entityId = new DeviceId(edgeEvent.getEntityId()); + entityId = new DeviceId(entityUUID); break; case ASSET: - entityId = new AssetId(edgeEvent.getEntityId()); + entityId = new AssetId(entityUUID); break; case ENTITY_VIEW: - entityId = new EntityViewId(edgeEvent.getEntityId()); + entityId = new EntityViewId(entityUUID); break; case DASHBOARD: - entityId = new DashboardId(edgeEvent.getEntityId()); + entityId = new DashboardId(entityUUID); break; case TENANT: - entityId = TenantId.fromUUID(edgeEvent.getEntityId()); + entityId = TenantId.fromUUID(entityUUID); break; case CUSTOMER: - entityId = new CustomerId(edgeEvent.getEntityId()); + entityId = new CustomerId(entityUUID); break; case USER: - entityId = new UserId(edgeEvent.getEntityId()); + entityId = new UserId(entityUUID); break; case EDGE: - entityId = new EdgeId(edgeEvent.getEntityId()); + entityId = new EdgeId(entityUUID); break; default: - log.warn("Unsupported edge event type [{}]", edgeEvent); + log.warn("Unsupported edge event type [{}]", entityType); return null; } - return constructEntityDataProtoMsg(entityId, edgeEvent.getAction(), - JsonUtils.parse(JacksonUtil.OBJECT_MAPPER.writeValueAsString(edgeEvent.getBody()))); - } - - private DownlinkMsg constructEntityDataProtoMsg(EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) { - EntityDataProto entityDataProto = entityDataMsgConstructor.constructEntityDataMsg(entityId, actionType, entityData); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addEntityData(entityDataProto) - .build(); + JsonElement entityData = JsonParser.parseString(JacksonUtil.OBJECT_MAPPER.writeValueAsString(body)); + return entityDataMsgConstructor.constructEntityDataMsg(entityId, actionType, entityData); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java new file mode 100644 index 0000000000..66c5596545 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.telemetry; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EntityDataProto; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Component +@Slf4j +@TbCoreComponent +public class TelemetryEdgeProcessor extends BaseTelemetryProcessor { + + @Override + protected String getMsgSourceKey() { + return DataConstants.EDGE_MSG_SOURCE; + } + + public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { + EntityType entityType = EntityType.valueOf(edgeEvent.getType().name()); + EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto(entityType, edgeEvent.getEntityId(), + edgeEvent.getAction(), edgeEvent.getBody()); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addEntityData(entityDataProto) + .build(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java index 390fafe464..9070686fd6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.user; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -29,6 +29,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java index b74e3c1cec..a429816a31 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.widget; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java index 10807b7b5c..3caad57081 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor; +package org.thingsboard.server.service.edge.rpc.processor.widget; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java index 4be7ddb1c8..4dffe6e058 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -52,13 +53,10 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.asset.AssetProfileService; -import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; @@ -93,24 +91,15 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { private AttributesService attributesService; @Autowired - private RelationService relationService; - + private TimeseriesService timeseriesService; + @Autowired - private DeviceService deviceService; - - @Autowired - private AssetService assetService; + private RelationService relationService; @Lazy @Autowired private TbEntityViewService entityViewService; - @Autowired - private DeviceProfileService deviceProfileService; - - @Autowired - private AssetProfileService assetProfileService; - @Autowired private WidgetsBundleService widgetsBundleService; @@ -141,77 +130,92 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { EntityId entityId = EntityIdFactory.getByTypeAndUuid( EntityType.valueOf(attributesRequestMsg.getEntityType()), new UUID(attributesRequestMsg.getEntityIdMSB(), attributesRequestMsg.getEntityIdLSB())); - final EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType()); - if (type == null) { + final EdgeEventType entityType = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType()); + if (entityType == null) { log.warn("[{}] Type doesn't supported {}", tenantId, entityId.getEntityType()); return Futures.immediateFuture(null); } - SettableFuture futureToSet = SettableFuture.create(); String scope = attributesRequestMsg.getScope(); ListenableFuture> findAttrFuture = attributesService.findAll(tenantId, entityId, scope); - Futures.addCallback(findAttrFuture, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable List ssAttributes) { - if (ssAttributes == null || ssAttributes.isEmpty()) { - log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId, - edge.getName(), - entityId.getEntityType(), - entityId.getId()); - futureToSet.set(null); - return; - } + return Futures.transformAsync(findAttrFuture, ssAttributes + -> processEntityAttributesAndAddToEdgeQueue(tenantId, entityId, edge, entityType, scope, ssAttributes, attributesRequestMsg), + dbCallbackExecutorService); + } - try { - Map entityData = new HashMap<>(); - ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - for (AttributeKvEntry attr : ssAttributes) { - if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey()) - && !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) { - continue; - } - if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { - attributes.put(attr.getKey(), attr.getBooleanValue().get()); - } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { - attributes.put(attr.getKey(), attr.getDoubleValue().get()); - } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) { - attributes.put(attr.getKey(), attr.getLongValue().get()); - } else { - attributes.put(attr.getKey(), attr.getValueAsString()); - } + private ListenableFuture processEntityAttributesAndAddToEdgeQueue(TenantId tenantId, EntityId entityId, Edge edge, + EdgeEventType entityType, String scope, List ssAttributes, + AttributesRequestMsg attributesRequestMsg) { + try { + ListenableFuture future; + if (ssAttributes == null || ssAttributes.isEmpty()) { + log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId, + edge.getName(), + entityId.getEntityType(), + entityId.getId()); + future = Futures.immediateFuture(null); + } else { + Map entityData = new HashMap<>(); + ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + for (AttributeKvEntry attr : ssAttributes) { + if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey()) + && !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) { + continue; } + if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { + attributes.put(attr.getKey(), attr.getBooleanValue().get()); + } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { + attributes.put(attr.getKey(), attr.getDoubleValue().get()); + } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) { + attributes.put(attr.getKey(), attr.getLongValue().get()); + } else { + attributes.put(attr.getKey(), attr.getValueAsString()); + } + } + if (attributes.size() > 0) { entityData.put("kv", attributes); entityData.put("scope", scope); JsonNode body = JacksonUtil.OBJECT_MAPPER.valueToTree(entityData); log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body); - ListenableFuture future = saveEdgeEvent(tenantId, edge.getId(), type, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable Void unused) { - futureToSet.set(null); - } - - @Override - public void onFailure(Throwable throwable) { - String errMsg = String.format("[%s] Failed to save edge event [%s]", edge.getId(), attributesRequestMsg); - log.error(errMsg, throwable); - futureToSet.setException(new RuntimeException(errMsg, throwable)); - } - }, dbCallbackExecutorService); - } catch (Exception e) { - String errMsg = String.format("[%s] Failed to save attribute updates to the edge [%s]", edge.getId(), attributesRequestMsg); - log.error(errMsg, e); - futureToSet.setException(new RuntimeException(errMsg, e)); + future = saveEdgeEvent(tenantId, edge.getId(), entityType, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body); + } else { + future = Futures.immediateFuture(null); } } - - @Override - public void onFailure(Throwable t) { - String errMsg = String.format("[%s] Can't find attributes [%s]", edge.getId(), attributesRequestMsg); - log.error(errMsg, t); - futureToSet.setException(new RuntimeException(errMsg, t)); + return Futures.transformAsync(future, v -> processLatestTimeseriesAndAddToEdgeQueue(tenantId, entityId, edge, entityType), dbCallbackExecutorService); + } catch (Exception e) { + String errMsg = String.format("[%s] Failed to save attribute updates to the edge [%s]", edge.getId(), attributesRequestMsg); + log.error(errMsg, e); + return Futures.immediateFailedFuture(new RuntimeException(errMsg, e)); + } + } + + private ListenableFuture processLatestTimeseriesAndAddToEdgeQueue(TenantId tenantId, EntityId entityId, Edge edge, + EdgeEventType entityType) { + ListenableFuture> getAllLatestFuture = timeseriesService.findAllLatest(tenantId, entityId); + return Futures.transformAsync(getAllLatestFuture, tsKvEntries -> { + if (tsKvEntries == null || tsKvEntries.isEmpty()) { + log.trace("[{}][{}] No timeseries found for entity {} [{}]", tenantId, + edge.getName(), + entityId.getEntityType(), + entityId.getId()); + return Futures.immediateFuture(null); } + Map> tsData = new HashMap<>(); + for (TsKvEntry tsKvEntry : tsKvEntries) { + if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(tsKvEntry.getKey())) { + continue; + } + tsData.computeIfAbsent(tsKvEntry.getTs(), k -> new HashMap<>()).put(tsKvEntry.getKey(), tsKvEntry.getValue()); + } + List> futures = new ArrayList<>(); + for (Map.Entry> entry : tsData.entrySet()) { + Map entityBody = new HashMap<>(); + entityBody.put("data", entry.getValue()); + entityBody.put("ts", entry.getKey()); + futures.add(saveEdgeEvent(tenantId, edge.getId(), entityType, EdgeEventActionType.TIMESERIES_UPDATED, entityId, JacksonUtil.valueToTree(entityBody))); + } + return Futures.transform(Futures.allAsList(futures), v -> null, dbCallbackExecutorService); }, dbCallbackExecutorService); - return futureToSet; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java index 4d512234bd..a8932aa993 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java index 9f34c061fe..11ff6a3321 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; @@ -62,18 +63,18 @@ public abstract class AbstractTbEntityService { protected EdgeService edgeService; @Autowired protected AlarmService alarmService; - @Autowired + @Autowired @Lazy protected AlarmSubscriptionService alarmSubscriptionService; @Autowired protected CustomerService customerService; @Autowired protected TbClusterService tbClusterService; - @Autowired(required = false) + @Autowired(required = false) @Lazy private EntitiesVersionControlService vcService; protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { ListenableFuture> alarmsFuture = - alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, false)); + alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, null, false)); ListenableFuture> alarmIdsFuture = Futures.transform(alarmsFuture, page -> page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 8f8531a5b4..fe4f761f81 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -130,13 +132,9 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS public void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, CustomerId customerId, E entity, ActionType actionType, - User user, boolean sendToEdge, - Object... additionalInfo) { + User user, Object... additionalInfo) { logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); - - if (sendToEdge) { - sendEntityNotificationMsg(tenantId, entityId, edgeTypeByActionType(actionType), JacksonUtil.toString(customerId)); - } + sendEntityNotificationMsg(tenantId, entityId, edgeTypeByActionType(actionType), JacksonUtil.toString(customerId)); } @Override @@ -224,11 +222,16 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } @Override - public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo) { + public void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo) { logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo); sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType)); } + @Override + public void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user) { + logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), actionType, user, alarmComment); + } + @Override public void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, I entityId, E entity, User user, @@ -306,6 +309,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS return EdgeEventActionType.ALARM_ACK; case ALARM_CLEAR: return EdgeEventActionType.ALARM_CLEAR; + case ALARM_ASSIGN: + return EdgeEventActionType.ALARM_ASSIGN; + case ALARM_UNASSIGN: + return EdgeEventActionType.ALARM_UNASSIGN; case DELETED: return EdgeEventActionType.DELETED; case RELATION_ADD_OR_UPDATE: diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java index 7c265a4fa3..609a61b903 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java index dc6e148cab..c5f8f85831 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -73,8 +75,7 @@ public interface TbNotificationEntityService { void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, CustomerId customerId, E entity, ActionType actionType, - User user, boolean sendToEdge, - Object... additionalInfo); + User user, Object... additionalInfo); void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, CustomerId customerId, EdgeId edgeId, @@ -100,7 +101,10 @@ public interface TbNotificationEntityService { void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, User user, Object... additionalInfo); - void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo); + void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo); + + void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user); + void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, I entityId, E entity, User user, diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java new file mode 100644 index 0000000000..6f86632ffd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.alarm; + +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.dao.alarm.AlarmCommentService; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; + +@Service +@AllArgsConstructor +public class DefaultTbAlarmCommentService extends AbstractTbEntityService implements TbAlarmCommentService{ + + @Autowired + private AlarmCommentService alarmCommentService; + + @Override + public AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException { + ActionType actionType = alarmComment.getId() == null ? ActionType.ADDED_COMMENT : ActionType.UPDATED_COMMENT; + if (user != null) { + alarmComment.setUserId(user.getId()); + } + try { + AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment)); + notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, actionType, user); + return savedAlarmComment; + } catch (Exception e) { + notificationEntityService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM), alarm, actionType, user, e, alarmComment); + throw e; + } + } + + @Override + public void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException { + if (alarmComment.getType() == AlarmCommentType.OTHER) { + alarmComment.setType(AlarmCommentType.SYSTEM); + alarmComment.setUserId(null); + alarmComment.setComment(JacksonUtil.newObjectNode().put("text", + String.format("User %s deleted his comment", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))); + AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.saveAlarmComment(alarm.getTenantId(), alarmComment)); + notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, ActionType.DELETED_COMMENT, user); + } else { + throw new ThingsboardException("System comment could not be deleted", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 8157eff3e5..36e8c9f188 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,36 +15,72 @@ */ package org.thingsboard.server.service.entitiy.alarm; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.AlarmAssignee; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; +import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import java.util.List; @Service @AllArgsConstructor +@Slf4j public class DefaultTbAlarmService extends AbstractTbEntityService implements TbAlarmService { + @Autowired + protected TbAlarmCommentService alarmCommentService; + @Override public Alarm save(Alarm alarm, User user) throws ThingsboardException { ActionType actionType = alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = alarm.getTenantId(); try { - Alarm savedAlarm = checkNotNull(alarmSubscriptionService.createOrUpdateAlarm(alarm)); - notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user); - return savedAlarm; + AlarmApiCallResult result; + if (alarm.getId() == null) { + result = alarmSubscriptionService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, user.getId())); + } else { + result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm, user.getId())); + } + if (!result.isSuccessful()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } + actionType = result.isCreated() ? ActionType.ADDED : ActionType.UPDATED; + if (result.isModified()) { + notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), actionType, user); + } + AlarmInfo resultAlarm = result.getAlarm(); + if (alarm.isAcknowledged() && !resultAlarm.isAcknowledged()) { + resultAlarm = ack(resultAlarm, alarm.getAckTs(), user); + } + if (alarm.isCleared() && !resultAlarm.isCleared()) { + resultAlarm = clear(resultAlarm, alarm.getClearTs(), user); + } + UserId newAssignee = alarm.getAssigneeId(); + UserId curAssignee = resultAlarm.getAssigneeId(); + if (newAssignee != null && !newAssignee.equals(curAssignee)) { + resultAlarm = assign(resultAlarm, newAssignee, alarm.getAssignTs(), user); + } else if (newAssignee == null && curAssignee != null) { + resultAlarm = unassign(alarm, alarm.getAssignTs(), user); + } + return new Alarm(resultAlarm); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); throw e; @@ -52,27 +88,126 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } @Override - public ListenableFuture ack(Alarm alarm, User user) { - long ackTs = System.currentTimeMillis(); - ListenableFuture future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs); - return Futures.transform(future, result -> { - alarm.setAckTs(ackTs); - alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); - notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user); - return null; - }, MoreExecutors.directExecutor()); + public AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException { + return ack(alarm, System.currentTimeMillis(), user); + } + + @Override + public AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(ackTs)); + if (!result.isSuccessful()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } + if (result.isModified()) { + AlarmComment alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was acknowledged by user %s", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName())) + .put("userId", user.getId().toString()) + .put("subtype", "ACK")) + .build(); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, user); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ACK, user); + } else { + throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + return result.getAlarm(); + } + + @Override + public AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException { + return clear(alarm, System.currentTimeMillis(), user); } @Override - public ListenableFuture clear(Alarm alarm, User user) { - long clearTs = System.currentTimeMillis(); - ListenableFuture future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs); - return Futures.transform(future, result -> { - alarm.setClearTs(clearTs); - alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); - notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user); - return null; - }, MoreExecutors.directExecutor()); + public AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(clearTs), null); + if (!result.isSuccessful()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } + if (result.isCleared()) { + AlarmComment alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was cleared by user %s", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName())) + .put("userId", user.getId().toString()) + .put("subtype", "CLEAR")) + .build(); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, user); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_CLEAR, user); + } else { + throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + return result.getAlarm(); + } + + @Override + public AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, getOrDefault(assignTs)); + if (!result.isSuccessful()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } + AlarmInfo alarmInfo = result.getAlarm(); + if (result.isModified()) { + AlarmAssignee assignee = alarmInfo.getAssignee(); + AlarmComment alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was assigned by user %s to user %s", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName(), + (assignee.getFirstName() == null || assignee.getLastName() == null) ? assignee.getEmail() : assignee.getFirstName() + " " + assignee.getLastName())) + .put("userId", user.getId().toString()) + .put("assigneeId", assignee.getId().toString()) + .put("subtype", "ASSIGN")) + .build(); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, user); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGN, user); + } else { + throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + return alarmInfo; + } + + @Override + public AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(unassignTs)); + if (!result.isSuccessful()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } + AlarmInfo alarmInfo = result.getAlarm(); + if (result.isModified()) { + AlarmComment alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was unassigned by user %s", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName())) + .put("userId", user.getId().toString()) + .put("subtype", "ASSIGN")) + .build(); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, user); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGN, user); + } else { + throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + return alarmInfo; } @Override @@ -83,4 +218,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb relatedEdgeIds, user, JacksonUtil.toString(alarm)); return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId()); } -} \ No newline at end of file + + private static long getOrDefault(long ts) { + return ts > 0 ? ts : System.currentTimeMillis(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java new file mode 100644 index 0000000000..a2bca133cb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.alarm; + +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.exception.ThingsboardException; + +public interface TbAlarmCommentService { + AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; + + void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index 8cd8d0b49d..a2ae9c8cc7 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,27 @@ */ package org.thingsboard.server.service.entitiy.alarm; -import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.UserId; public interface TbAlarmService { Alarm save(Alarm entity, User user) throws ThingsboardException; - ListenableFuture ack(Alarm alarm, User user); + AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException; - ListenableFuture clear(Alarm alarm, User user); + AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException; + + AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException; + + AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException; + + AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException; + + AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException; Boolean delete(Alarm alarm, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index 22edb2a01f..ab57ecd050 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb try { Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, user, true, assetId.toString(), customerId.toString(), customer.getName()); + actionType, user, assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { @@ -115,7 +115,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(tenantId, assetId)); CustomerId customerId = customer.getId(); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, user, true, assetId.toString(), customerId.toString(), customer.getName()); + actionType, user, assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { @@ -131,7 +131,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, publicCustomer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, savedAsset.getCustomerId(), savedAsset, - actionType, user, false, actionType.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); + actionType, user, assetId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedAsset; } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java index 303aabc35b..b966eb8c6a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java index d053b99a3b..b662994451 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java index e451b3245a..191acf686d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java index 986d67dde7..b71cfd222e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java index 870de10efc..e4570e73ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index 7fadab17ba..fa5b967bd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, - actionType, user, true, dashboardId.toString(), customerId.toString(), customer.getName()); + actionType, user, dashboardId.toString(), customerId.toString(), customer.getName()); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, @@ -102,7 +102,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, publicCustomer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, publicCustomer.getId(), savedDashboard, - actionType, user, false, dashboardId.toString(), + actionType, user, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { @@ -120,7 +120,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, publicCustomer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, publicCustomer.getId(), dashboard, - actionType, user, false, dashboardId.toString(), + actionType, user, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { @@ -160,14 +160,14 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - actionType, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); + actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - ActionType.UNASSIGNED_FROM_CUSTOMER, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); + ActionType.UNASSIGNED_FROM_CUSTOMER, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } @@ -197,7 +197,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, - actionType, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); + actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } @@ -227,7 +227,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, - actionType, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); + actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } @@ -282,7 +282,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customer.getId(), savedDashboard, - actionType, user, true, dashboardId.toString(), customer.getId().toString(), customer.getName()); + actionType, user, dashboardId.toString(), customer.getId().toString(), customer.getName()); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java index 73d7ea5d44..fffe50014d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index 25053676fd..2ab8de9438 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,7 +117,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T try { Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, - actionType, user, true, deviceId.toString(), customerId.toString(), customer.getName()); + actionType, user, deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { @@ -137,7 +137,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T CustomerId customerId = customer.getId(); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, - actionType, user, true, deviceId.toString(), customerId.toString(), customer.getName()); + actionType, user, deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { @@ -155,7 +155,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, publicCustomer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, savedDevice.getCustomerId(), savedDevice, - actionType, user, false, deviceId.toString(), + actionType, user, deviceId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDevice; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java index 3ffc120067..e9665c6e3e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java index 650b35db0d..faa6a69983 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java index 03ad8f75cb..fdcbec9c0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java index b3b70a174e..c51ae3352e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, edgeId, customerId, savedEdge, - actionType, user, true, edgeId.toString(), customerId.toString(), customer.getName()); + actionType, user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { @@ -109,7 +109,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE try { Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(tenantId, edgeId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, edgeId, customerId, savedEdge, - actionType, user, true, edgeId.toString(), customerId.toString(), customer.getName()); + actionType, user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java index b7cc0a5072..f458be5a1f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java index 8a32392ebc..b978b730fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java index 09dfd35fe4..2caee86d0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 3ae5fe67f2..48e6c4b634 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -149,7 +149,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customerId, savedEntityView, - ActionType.ASSIGNED_TO_CUSTOMER, user, true, entityViewId.toString(), customerId.toString(), customer.getName()); + ActionType.ASSIGNED_TO_CUSTOMER, user, entityViewId.toString(), customerId.toString(), customer.getName()); return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), @@ -159,18 +159,19 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen } @Override - public EntityView assignEntityViewToPublicCustomer(TenantId tenantId, CustomerId customerId, Customer publicCustomer, - EntityViewId entityViewId, User user) throws ThingsboardException { + public EntityView assignEntityViewToPublicCustomer(TenantId tenantId, EntityViewId entityViewId, User user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customerId, savedEntityView, - ActionType.ASSIGNED_TO_CUSTOMER, user, false, savedEntityView.getEntityId().toString(), + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, savedEntityView.getCustomerId(), savedEntityView, + actionType, user, savedEntityView.getId().toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), - ActionType.ASSIGNED_TO_CUSTOMER, user, e, entityViewId.toString()); + actionType, user, e, entityViewId.toString()); throw e; } } @@ -211,14 +212,17 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen @Override public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; try { EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(tenantId, entityViewId)); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customer.getId(), savedEntityView, - ActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customer.getId().toString(), customer.getName()); + actionType, user, savedEntityView.getId().toString(), customer.getId().toString(), customer.getName()); + return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), - ActionType.UNASSIGNED_FROM_CUSTOMER, user, e, entityViewId.toString()); + actionType, user, e, entityViewId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java index c4dbedd2eb..d0c18ce98f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,8 +39,7 @@ public interface TbEntityViewService extends ComponentLifecycleListener { EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException; - EntityView assignEntityViewToPublicCustomer(TenantId tenantId, CustomerId customerId, Customer publicCustomer, - EntityViewId entityViewId, User user) throws ThingsboardException; + EntityView assignEntityViewToPublicCustomer(TenantId tenantId, EntityViewId entityViewId, User user) throws ThingsboardException; EntityView assignEntityViewToEdge(TenantId tenantId, CustomerId customerId, EntityViewId entityViewId, Edge edge, User user) throws ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java index ac825c211e..4d3c61e615 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java index aec2f20d0c..3057da88f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java index 4355d998bb..6997ee8d98 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java index 86f82b1236..34651367c7 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 01a2eee664..19d4126989 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java index 2dc8fa452c..8a6f793cb0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java index a25c4acad7..1219a5b929 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java index 96d18e3577..c45d3314cd 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java new file mode 100644 index 0000000000..795ed1f145 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java @@ -0,0 +1,202 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.user; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.HasTitle; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.settings.AbstractUserDashboardInfo; +import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo; +import org.thingsboard.server.common.data.settings.StarredDashboardInfo; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsType; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.user.UserSettingsService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@AllArgsConstructor +@Slf4j +public class DefaultTbUserSettingsService implements TbUserSettingsService { + + private static final int MAX_DASHBOARD_INFO_LIST_SIZE = 10; + private static final Predicate EMPTY_TITLE = i -> StringUtils.isEmpty(i.getTitle()); + + private final UserSettingsService settingsService; + private final DashboardService dashboardService; + + @Override + public UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings) { + return settingsService.saveUserSettings(tenantId, userSettings); + } + + @Override + public void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings) { + settingsService.updateUserSettings(tenantId, userId, type, settings); + } + + @Override + public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) { + return settingsService.findUserSettings(tenantId, userId, type); + } + + @Override + public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List jsonPaths) { + settingsService.deleteUserSettings(tenantId, userId, type, jsonPaths); + } + + @Override + public UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id) { + UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS); + if (us == null) { + return UserDashboardsInfo.EMPTY; + } + UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); + return refreshDashboardTitles(tenantId, stored); + } + + @Override + public UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action) { + UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS); + UserDashboardsInfo stored = null; + if (us != null) { + stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); + } + if (stored == null) { + stored = new UserDashboardsInfo(); + } + + switch (action) { + case STAR: + addToStarred(stored, dashboardId); + break; + case UNSTAR: + removeFromStarred(stored, dashboardId); + break; + case VISIT: + addToVisited(stored, dashboardId); + break; + } + + stored = refreshDashboardTitles(tenantId, stored); + + us = new UserSettings(); + us.setUserId(id); + us.setType(UserSettingsType.VISITED_DASHBOARDS); + us.setSettings(JacksonUtil.valueToTree(stored)); + saveUserSettings(tenantId, us); + return stored; + } + + private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) { + UUID id = dashboardId.getId(); + long ts = System.currentTimeMillis(); + var opt = stored.getLast().stream().filter(filterById(id)).findFirst(); + if (opt.isPresent()) { + opt.get().setLastVisited(ts); + } else { + var newInfo = new LastVisitedDashboardInfo(); + newInfo.setId(id); + newInfo.setStarred(stored.getStarred().stream().anyMatch(filterById(id))); + newInfo.setLastVisited(System.currentTimeMillis()); + stored.getLast().add(newInfo); + } + stored.getLast().sort(Comparator.comparing(LastVisitedDashboardInfo::getLastVisited).reversed()); + if (stored.getLast().size() > MAX_DASHBOARD_INFO_LIST_SIZE) { + stored.setLast(stored.getLast().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList())); + } + } + + private void removeFromStarred(UserDashboardsInfo stored, DashboardId dashboardId) { + UUID id = dashboardId.getId(); + stored.getStarred().removeIf(filterById(id)); + stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst().ifPresent(d -> d.setStarred(false)); + } + + private void addToStarred(UserDashboardsInfo stored, DashboardId dashboardId) { + UUID id = dashboardId.getId(); + long ts = System.currentTimeMillis(); + var opt = stored.getStarred().stream().filter(filterById(id)).findFirst(); + if (opt.isPresent()) { + opt.get().setStarredAt(ts); + } else { + var newInfo = new StarredDashboardInfo(); + newInfo.setId(id); + newInfo.setStarredAt(System.currentTimeMillis()); + stored.getStarred().add(newInfo); + } + stored.getStarred().sort(Comparator.comparing(StarredDashboardInfo::getStarredAt).reversed()); + if (stored.getStarred().size() > MAX_DASHBOARD_INFO_LIST_SIZE) { + stored.setStarred(stored.getStarred().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList())); + } + Set starredMap = + stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).collect(Collectors.toSet()); + stored.getLast().forEach(d -> d.setStarred(starredMap.contains(d.getId()))); + } + + private Predicate filterById(UUID id) { + return d -> id.equals(d.getId()); + } + + private UserDashboardsInfo refreshDashboardTitles(TenantId tenantId, UserDashboardsInfo stored) { + if (stored == null) { + return UserDashboardsInfo.EMPTY; + } + stored.getLast().forEach(i -> i.setTitle(null)); + stored.getStarred().forEach(i -> i.setTitle(null)); + + Set uniqueIds = new HashSet<>(); + stored.getLast().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add); + stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add); + + Map dashboardTitles = new HashMap<>(); + uniqueIds.forEach(id -> { + var title = dashboardService.findDashboardTitleById(tenantId, new DashboardId(id)); + if (StringUtils.isNotEmpty(title)) { + dashboardTitles.put(id, title); + } + } + ); + + stored.getLast().forEach(i -> i.setTitle(dashboardTitles.get(i.getId()))); + stored.getLast().removeIf(EMPTY_TITLE); + stored.getStarred().forEach(i -> i.setTitle(dashboardTitles.get(i.getId()))); + stored.getStarred().removeIf(EMPTY_TITLE); + return stored; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index a3df4b816b..c83c48cc8a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java index 765733ba3e..3ef79371d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java new file mode 100644 index 0000000000..a81dbcad2f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.user; + +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsType; + +import java.util.List; + +public interface TbUserSettingsService { + + void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings); + + UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); + + UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type); + + void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List jsonPaths); + + UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id); + + UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action); +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java index 1706598b9c..ffd667bfec 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java index 2820934aa3..446010b61c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java index 84ac8c5ee4..910f8d7d8e 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java index 83295171d2..3e092eefae 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/executors/GrpcCallbackExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/GrpcCallbackExecutorService.java index b249ad091d..2d2e7ee9d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/GrpcCallbackExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/GrpcCallbackExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java new file mode 100644 index 0000000000..81ad36a4a1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.executors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class NotificationExecutorService extends AbstractListeningExecutor { + + @Value("${notification_system.thread_pool_size:10}") + private int threadPoolSize; + + @Override + protected int getThreadPollSize() { + return threadPoolSize; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java b/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java index e812ec4e46..3687da1795 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayNotificationsService.java b/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayNotificationsService.java index 8eea83c767..52a7d3245a 100644 --- a/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayNotificationsService.java +++ b/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayNotificationsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayNotificationsService.java b/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayNotificationsService.java index d6b20c8858..bb1fbe5391 100644 --- a/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayNotificationsService.java +++ b/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayNotificationsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java index c149413f5c..ce20b2176b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java index b223a20867..07ff503b4f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java index 1d843e5e2d..9d266ad4dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java index dfdd05e829..764abae52e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java index e77a034d8a..f623aba033 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java index 25218fa35b..5c7c51eb2c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsLatestDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsLatestDatabaseSchemaService.java index 181fb41610..f95699b418 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsLatestDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsLatestDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java index beb4135cb9..c11cc3614a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java index 2d90b8a16a..70f161d2e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseSchemaService.java index 8dde1682b2..59774b2088 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java index 16c5bf9cda..38e8dd639a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java b/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java index b3ce77ac7a..7718354106 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 9507912c39..bd68aad316 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; @@ -82,13 +83,14 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; @@ -97,6 +99,7 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -171,6 +174,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private JwtSettingsService jwtSettingsService; + @Autowired + private NotificationSettingsService notificationSettingsService; + + @Autowired + private NotificationTargetService notificationTargetService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -203,8 +212,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedRuleEngineTenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); TenantProfileQueueConfiguration mainQueueConfiguration = new TenantProfileQueueConfiguration(); - mainQueueConfiguration.setName("Main"); - mainQueueConfiguration.setTopic("tb_rule_engine.main"); + mainQueueConfiguration.setName(DataConstants.MAIN_QUEUE_NAME); + mainQueueConfiguration.setTopic(DataConstants.MAIN_QUEUE_TOPIC); mainQueueConfiguration.setPollInterval(25); mainQueueConfiguration.setPartitions(10); mainQueueConfiguration.setConsumerPerPartition(true); @@ -598,12 +607,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override public void createQueues() { - Queue mainQueue = queueService.findQueueByTenantIdAndName(TenantId.SYS_TENANT_ID, "Main"); + Queue mainQueue = queueService.findQueueByTenantIdAndName(TenantId.SYS_TENANT_ID, DataConstants.MAIN_QUEUE_NAME); if (mainQueue == null) { mainQueue = new Queue(); mainQueue.setTenantId(TenantId.SYS_TENANT_ID); - mainQueue.setName("Main"); - mainQueue.setTopic("tb_rule_engine.main"); + mainQueue.setName(DataConstants.MAIN_QUEUE_NAME); + mainQueue.setTopic(DataConstants.MAIN_QUEUE_TOPIC); mainQueue.setPollInterval(25); mainQueue.setPartitions(10); mainQueue.setConsumerPerPartition(true); @@ -622,12 +631,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { queueService.saveQueue(mainQueue); } - Queue highPriorityQueue = queueService.findQueueByTenantIdAndName(TenantId.SYS_TENANT_ID, "HighPriority"); + Queue highPriorityQueue = queueService.findQueueByTenantIdAndName(TenantId.SYS_TENANT_ID, DataConstants.HP_QUEUE_NAME); if (highPriorityQueue == null) { highPriorityQueue = new Queue(); highPriorityQueue.setTenantId(TenantId.SYS_TENANT_ID); - highPriorityQueue.setName("HighPriority"); - highPriorityQueue.setTopic("tb_rule_engine.hp"); + highPriorityQueue.setName(DataConstants.HP_QUEUE_NAME); + highPriorityQueue.setTopic(DataConstants.HP_QUEUE_TOPIC); highPriorityQueue.setPollInterval(25); highPriorityQueue.setPartitions(10); highPriorityQueue.setConsumerPerPartition(true); @@ -646,12 +655,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { queueService.saveQueue(highPriorityQueue); } - Queue sequentialByOriginatorQueue = queueService.findQueueByTenantIdAndName(TenantId.SYS_TENANT_ID, "SequentialByOriginator"); + Queue sequentialByOriginatorQueue = queueService.findQueueByTenantIdAndName(TenantId.SYS_TENANT_ID, DataConstants.SQ_QUEUE_NAME); if (sequentialByOriginatorQueue == null) { sequentialByOriginatorQueue = new Queue(); sequentialByOriginatorQueue.setTenantId(TenantId.SYS_TENANT_ID); - sequentialByOriginatorQueue.setName("SequentialByOriginator"); - sequentialByOriginatorQueue.setTopic("tb_rule_engine.sq"); + sequentialByOriginatorQueue.setName(DataConstants.SQ_QUEUE_NAME); + sequentialByOriginatorQueue.setTopic(DataConstants.SQ_QUEUE_TOPIC); sequentialByOriginatorQueue.setPollInterval(25); sequentialByOriginatorQueue.setPartitions(10); sequentialByOriginatorQueue.setPackProcessingTimeout(2000); @@ -671,4 +680,19 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } } + @Override + public void createDefaultNotificationConfigs() { + log.info("Creating default notification configs for system admin"); + if (notificationTargetService.findNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(1)).getTotalElements() == 0) { + notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID); + } + PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); + log.info("Creating default notification configs for all tenants"); + for (TenantId tenantId : tenants) { + if (notificationTargetService.findNotificationTargetsByTenantId(tenantId, new PageLink(1)).getTotalElements() == 0) { + notificationSettingsService.createDefaultNotificationConfigs(tenantId); + } + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java index ff779d460e..8d040d365e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 38ff5aacec..5ab7324192 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; @@ -30,6 +33,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -41,9 +45,11 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; import java.util.Optional; import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; +import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource; /** * Created by ashvayka on 18.04.18. @@ -59,17 +65,16 @@ public class InstallScripts { public static final String JSON_DIR = "json"; public static final String SYSTEM_DIR = "system"; public static final String TENANT_DIR = "tenant"; + public static final String EDGE_DIR = "edge"; public static final String DEVICE_PROFILE_DIR = "device_profile"; public static final String DEMO_DIR = "demo"; public static final String RULE_CHAINS_DIR = "rule_chains"; public static final String WIDGET_BUNDLES_DIR = "widget_bundles"; public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates"; public static final String DASHBOARDS_DIR = "dashboards"; - public static final String MODELS_DIR = "models"; + public static final String MODELS_LWM2M_DIR = "lwm2m-registry"; public static final String CREDENTIALS_DIR = "credentials"; - public static final String EDGE_MANAGEMENT = "edge_management"; - public static final String JSON_EXT = ".json"; public static final String XML_EXT = ".xml"; @@ -103,7 +108,7 @@ public class InstallScripts { } private Path getEdgeRuleChainsDir() { - return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR); + return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR); } public String getDataDir() { @@ -260,4 +265,43 @@ public class InstallScripts { ); } } + + public void loadSystemLwm2mResources() { + Path resourceLwm2mPath = Paths.get(dataDir, MODELS_LWM2M_DIR); + try (DirectoryStream dirStream = Files.newDirectoryStream(resourceLwm2mPath, path -> path.toString().endsWith(InstallScripts.XML_EXT))) { + dirStream.forEach( + path -> { + try { + String data = Base64.getEncoder().encodeToString(Files.readAllBytes(path)); + TbResource tbResource = new TbResource(); + tbResource.setTenantId(TenantId.SYS_TENANT_ID); + tbResource.setData(data); + tbResource.setResourceType(ResourceType.LWM2M_MODEL); + tbResource.setFileName(path.toFile().getName()); + doSaveLwm2mResource(tbResource); + } catch (Exception e) { + log.error("Unable to load resource lwm2m object model from file: [{}]", path.toString()); + throw new RuntimeException("resource lwm2m object model from file", e); + } + } + ); + } catch (Exception e) { + log.error("Unable to load resources lwm2m object model from file: [{}]", resourceLwm2mPath.toString()); + throw new RuntimeException("resource lwm2m object model from file", e); + } + } + + private void doSaveLwm2mResource(TbResource resource) throws ThingsboardException { + log.trace("Executing saveResource [{}]", resource); + if (StringUtils.isEmpty(resource.getData())) { + throw new DataValidationException("Resource data should be specified!"); + } + toLwm2mResource(resource); + TbResource foundResource = + resourceService.getResource(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey()); + if (foundResource == null) { + resourceService.saveResource(resource); + } + } } + diff --git a/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java b/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java index 5b8d772dca..b02a202bd2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java index 28e233c3e7..800dcd7e91 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 80665d544f..f5f212beb2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -677,6 +677,42 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.4.4": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + if (isOldSchema(conn, 3004002)) { + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.4", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + + try { + conn.createStatement().execute("VACUUM FULL ANALYZE alarm;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } + + try { + conn.createStatement().execute("ALTER TABLE asset_profile ADD COLUMN default_edge_rule_chain_id uuid"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } + try { + conn.createStatement().execute("ALTER TABLE device_profile ADD COLUMN default_edge_rule_chain_id uuid"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } + try { + conn.createStatement().execute("ALTER TABLE asset_profile ADD CONSTRAINT fk_default_edge_rule_chain_asset_profile FOREIGN KEY (default_edge_rule_chain_id) REFERENCES rule_chain(id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } + try { + conn.createStatement().execute("ALTER TABLE device_profile ADD CONSTRAINT fk_default_edge_rule_chain_device_profile FOREIGN KEY (default_edge_rule_chain_id) REFERENCES rule_chain(id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } + + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005000;"); + } + log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java index 7ae155402a..60513765a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java index f3ede624ff..8627fd4b14 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java index 003f544b56..6400463e11 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 041351f5e4..fb6b28592c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,4 +38,7 @@ public interface SystemDataLoaderService { void deleteSystemWidgetBundle(String bundleAlias) throws Exception; void createQueues(); + + void createDefaultNotificationConfigs(); + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java b/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java index 1125467082..455bd56309 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import javax.annotation.PostConstruct; @@ -39,7 +40,7 @@ public class TbRuleEngineQueueConfigService { @PostConstruct public void validate() { - queues.stream().filter(queue -> queue.getName().equals("Main")).findFirst().orElseThrow(() -> { + queues.stream().filter(queue -> queue.getName().equals(DataConstants.MAIN_QUEUE_NAME)).findFirst().orElseThrow(() -> { log.error("Main queue is not configured in thingsboard.yml"); return new RuntimeException("No \"Main\" queue configured!"); }); diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java index 409498cfd3..643fb917aa 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 16c054b2fc..205ec14591 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java index 4274f1dc65..8e92d0956f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/TsLatestDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/TsLatestDatabaseSchemaService.java index d74afa880c..9a84e0ff81 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TsLatestDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TsLatestDatabaseSchemaService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CQLStatementsParser.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CQLStatementsParser.java index 9883009fa1..666a0b972a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/cql/CQLStatementsParser.java +++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CQLStatementsParser.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java index add28452d3..4bcd52daa9 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java index e9f72a64bf..cdaf068585 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java index a003c4c625..1f897e253d 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnData.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnData.java index 7a6a014686..e8a1b54705 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnData.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnType.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnType.java index 206fb2d8e5..49eb277ab8 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnType.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumnType.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java index 38a3225778..b9f5d64513 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java index 7f3669fc69..745e3e4e97 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlTable.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java index 1aa7602a35..4357e6be63 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java index 42cdd2ebff..d4e992cffd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/TsLatestMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/TsLatestMigrateService.java index c32f08bfc7..51ca5d012a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/TsLatestMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/TsLatestMigrateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java index 83f53d9fb1..f321fb28d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/CacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/CacheCleanupService.java index 083b1d39b5..f85bc3ae22 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/CacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/CacheCleanupService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java index 909e1628ac..ae573b829a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 367bb049e5..bd840b2f52 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index ee34bd7f23..13943d5698 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -555,13 +556,13 @@ public class DefaultDataUpdateService implements DataUpdateService { }; private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) { - AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, false); + AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, null, false); PageData alarms = alarmDao.findAlarms(tenantId, alarmQuery); boolean hasNext = true; while (hasNext) { for (Alarm alarm : alarms.getData()) { if (alarm.getCustomerId() == null && alarm.getOriginator() != null) { - alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator())); + alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get()); alarmDao.save(tenantId, alarm); } if (processed.incrementAndGet() % 1000 == 0) { @@ -651,8 +652,8 @@ public class DefaultDataUpdateService implements DataUpdateService { private TenantProfileQueueConfiguration getMainQueueConfiguration() { TenantProfileQueueConfiguration mainQueueConfiguration = new TenantProfileQueueConfiguration(); - mainQueueConfiguration.setName("Main"); - mainQueueConfiguration.setTopic("tb_rule_engine.main"); + mainQueueConfiguration.setName(DataConstants.MAIN_QUEUE_NAME); + mainQueueConfiguration.setTopic(DataConstants.MAIN_QUEUE_TOPIC); mainQueueConfiguration.setPollInterval(25); mainQueueConfiguration.setPartitions(10); mainQueueConfiguration.setConsumerPerPartition(true); @@ -671,7 +672,7 @@ public class DefaultDataUpdateService implements DataUpdateService { return mainQueueConfiguration; } - private boolean getEnv(String name, boolean defaultValue) { + public static boolean getEnv(String name, boolean defaultValue) { String env = System.getenv(name); if (env == null) { return defaultValue; diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java index b2fb707791..f12b4b3c87 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java index 87179b26b5..dd24aa7797 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java index eee64084d6..d311e67431 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java index 31e60b3f18..2e80a4ef74 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 7b26af989c..55eae83af4 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ import org.thingsboard.rule.engine.api.TbEmail; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; -import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageRecordState; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -64,8 +64,6 @@ public class DefaultMailService implements MailService { public static final String MAIL_PROP = "mail."; public static final String TARGET_EMAIL = "targetEmail"; public static final String UTF_8 = "UTF-8"; - public static final int _10K = 10000; - public static final int _1M = 1000000; private final MessageSource messages; private final Configuration freemarkerConfig; @@ -335,7 +333,7 @@ public class DefaultMailService implements MailService { } @Override - public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageRecordState recordState) throws ThingsboardException { String subject = messages.getMessage("api.usage.state", null, Locale.US); Map model = new HashMap<>(); @@ -350,11 +348,11 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: - model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(recordState)); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: - model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(recordState)); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } @@ -406,10 +404,10 @@ public class DefaultMailService implements MailService { } } - private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { - String valueInM = getValueAsString(value); - String thresholdInM = getValueAsString(threshold); - switch (key) { + private String toWarningValueLabel(ApiUsageRecordState recordState) { + String valueInM = recordState.getValueAsString(); + String thresholdInM = recordState.getThresholdAsString(); + switch (recordState.getKey()) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: return valueInM + " out of " + thresholdInM + " allowed data points"; @@ -428,36 +426,26 @@ public class DefaultMailService implements MailService { } } - private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { - switch (key) { + private String toDisabledValueLabel(ApiUsageRecordState recordState) { + switch (recordState.getKey()) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return getValueAsString(value) + " data points"; + return recordState.getValueAsString() + " data points"; case TRANSPORT_MSG_COUNT: - return getValueAsString(value) + " messages"; + return recordState.getValueAsString() + " messages"; case JS_EXEC_COUNT: - return "JavaScript functions " + getValueAsString(value) + " times"; + return "JavaScript functions " + recordState.getValueAsString() + " times"; case RE_EXEC_COUNT: - return getValueAsString(value) + " Rule Engine messages"; + return recordState.getValueAsString() + " Rule Engine messages"; case EMAIL_EXEC_COUNT: - return getValueAsString(value) + " Email messages"; + return recordState.getValueAsString() + " Email messages"; case SMS_EXEC_COUNT: - return getValueAsString(value) + " SMS messages"; + return recordState.getValueAsString() + " SMS messages"; default: throw new RuntimeException("Not implemented!"); } } - private String getValueAsString(long value) { - if (value > _1M && value % _1M < _10K) { - return value / _1M + "M"; - } else if (value > _10K) { - return String.format("%.2fM", ((double) value) / 1000000); - } else { - return value + ""; - } - } - private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message, long timeout) throws ThingsboardException { try { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java b/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java index 8a8c79b51c..ededd90969 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java b/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java index ea761c144d..642af39f04 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java new file mode 100644 index 0000000000..b4fba04212 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -0,0 +1,422 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.AlreadySentException; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.msg.tools.TbRateLimitsException; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.NotificationsTopicService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.service.apiusage.limits.LimitedApi; +import org.thingsboard.server.service.apiusage.limits.RateLimitService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.executors.NotificationExecutorService; +import org.thingsboard.server.service.notification.channels.NotificationChannel; +import org.thingsboard.server.service.subscription.TbSubscriptionUtils; +import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +@SuppressWarnings({"UnstableApiUsage", "rawtypes"}) +public class DefaultNotificationCenter extends AbstractSubscriptionService implements NotificationCenter, NotificationChannel { + + private final NotificationTargetService notificationTargetService; + private final NotificationRequestService notificationRequestService; + private final NotificationService notificationService; + private final NotificationTemplateService notificationTemplateService; + private final NotificationSettingsService notificationSettingsService; + private final UserService userService; + private final NotificationExecutorService notificationExecutor; + private final DbCallbackExecutorService dbCallbackExecutorService; + private final NotificationsTopicService notificationsTopicService; + private final TbQueueProducerProvider producerProvider; + private final RateLimitService rateLimitService; + + private Map channels; + + @Override + public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest request, Consumer callback) { + if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) { + throw new TbRateLimitsException(EntityType.TENANT); + } + + NotificationTemplate notificationTemplate; + if (request.getTemplateId() != null) { + notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, request.getTemplateId()); + } else { + notificationTemplate = request.getTemplate(); + } + if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing"); + + List targets = request.getTargets().stream().map(NotificationTargetId::new) + .map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList()); + + NotificationRuleId ruleId = request.getRuleId(); + notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { + if (!template.isEnabled()) return; + if (!channels.get(deliveryMethod).check(tenantId)) { + throw new IllegalArgumentException("Unable to send notification via " + deliveryMethod.getName() + ": not configured or not working"); + } + if (ruleId == null) { + if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { + throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing"); + } + } + }); + + if (request.getAdditionalConfig() != null) { + NotificationRequestConfig config = request.getAdditionalConfig(); + if (config.getSendingDelayInSec() > 0 && request.getId() == null) { + request.setStatus(NotificationRequestStatus.SCHEDULED); + request = notificationRequestService.saveNotificationRequest(tenantId, request); + forwardToNotificationSchedulerService(tenantId, request.getId()); + return request; + } + } + NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); + + log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, request.getTargets()); + request.setStatus(NotificationRequestStatus.PROCESSING); + request = notificationRequestService.saveNotificationRequest(tenantId, request); + + NotificationProcessingContext ctx = NotificationProcessingContext.builder() + .tenantId(tenantId) + .request(request) + .template(notificationTemplate) + .settings(settings) + .build(); + + notificationExecutor.submit(() -> { + List> results = new ArrayList<>(); + + for (NotificationTarget target : targets) { + List> result = processForTarget(target, ctx); + results.addAll(result); + } + + Futures.whenAllComplete(results).run(() -> { + NotificationRequestId requestId = ctx.getRequest().getId(); + log.debug("[{}] Notification request processing is finished", requestId); + NotificationRequestStats stats = ctx.getStats(); + try { + notificationRequestService.updateNotificationRequest(tenantId, requestId, NotificationRequestStatus.SENT, stats); + } catch (Exception e) { + log.error("[{}] Failed to update stats for notification request", requestId, e); + } + + if (callback != null) { + try { + callback.accept(stats); + } catch (Exception e) { + log.error("Failed to process callback for notification request {}", requestId, e); + } + } + }, dbCallbackExecutorService); + }); + + return request; + } + + private List> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) { + Iterable recipients; + switch (target.getConfiguration().getType()) { + case PLATFORM_USERS: { + PlatformUsersNotificationTargetConfig targetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration(); + if (targetConfig.getUsersFilter().getType().isForRules()) { + recipients = new PageDataIterable<>(pageLink -> { + return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink); + }, 500); + } else { + recipients = new PageDataIterable<>(pageLink -> { + return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink); + }, 500); + } + break; + } + case SLACK: { + SlackNotificationTargetConfig targetConfig = (SlackNotificationTargetConfig) target.getConfiguration(); + recipients = List.of(targetConfig.getConversation()); + break; + } + default: { + recipients = Collections.emptyList(); + } + } + + Set deliveryMethods = new HashSet<>(ctx.getDeliveryMethods()); + deliveryMethods.removeIf(deliveryMethod -> !target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod)); + log.debug("[{}] Processing notification request for {} target ({}) for delivery methods {}", ctx.getRequest().getId(), target.getConfiguration().getType(), target.getId(), deliveryMethods); + + List> results = new ArrayList<>(); + if (!deliveryMethods.isEmpty()) { + for (NotificationRecipient recipient : recipients) { + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + ListenableFuture resultFuture = processForRecipient(deliveryMethod, recipient, ctx); + DonAsynchron.withCallback(resultFuture, result -> { + ctx.getStats().reportSent(deliveryMethod, recipient); + }, error -> { + ctx.getStats().reportError(deliveryMethod, error, recipient); + }); + results.add(resultFuture); + } + } + } + return results; + } + + private ListenableFuture processForRecipient(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient, NotificationProcessingContext ctx) { + if (ctx.getStats().contains(deliveryMethod, recipient.getId())) { + return Futures.immediateFailedFuture(new AlreadySentException()); + } + + DeliveryMethodNotificationTemplate processedTemplate; + try { + processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient); + } catch (Exception e) { + return Futures.immediateFailedFuture(e); + } + + NotificationChannel notificationChannel = channels.get(deliveryMethod); + log.trace("[{}] Sending {} notification for recipient {}", ctx.getRequest().getId(), deliveryMethod, recipient); + return notificationChannel.sendNotification(recipient, processedTemplate, ctx); + } + + @Override + public ListenableFuture sendNotification(User recipient, WebDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { + NotificationRequest request = ctx.getRequest(); + Notification notification = Notification.builder() + .requestId(request.getId()) + .recipientId(recipient.getId()) + .type(ctx.getNotificationTemplate().getNotificationType()) + .subject(processedTemplate.getSubject()) + .text(processedTemplate.getBody()) + .additionalConfig(processedTemplate.getAdditionalConfig()) + .info(request.getInfo()) + .status(NotificationStatus.SENT) + .build(); + try { + notification = notificationService.saveNotification(recipient.getTenantId(), notification); + } catch (Exception e) { + log.error("Failed to create notification for recipient {}", recipient.getId(), e); + return Futures.immediateFailedFuture(e); + } + + NotificationUpdate update = NotificationUpdate.builder() + .created(true) + .notification(notification) + .build(); + return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), update); + } + + @Override + public void sendBasicNotification(TenantId tenantId, UserId recipientId, String subject, String text) { + Notification notification = Notification.builder() + .recipientId(recipientId) + .type(NotificationType.GENERAL) + .subject(subject) + .text(text) + .status(NotificationStatus.SENT) + .build(); + notification = notificationService.saveNotification(TenantId.SYS_TENANT_ID, notification); + + NotificationUpdate update = NotificationUpdate.builder() + .created(true) + .notification(notification) + .build(); + onNotificationUpdate(tenantId, recipientId, update); + } + + @Override + public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { + boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); + if (updated) { + log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); + NotificationUpdate update = NotificationUpdate.builder() + .updated(true) + .notificationId(notificationId.getId()) + .newStatus(NotificationStatus.READ) + .build(); + onNotificationUpdate(tenantId, recipientId, update); + } + } + + @Override + public void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) { + int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, recipientId); + if (updatedCount > 0) { + log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId); + NotificationUpdate update = NotificationUpdate.builder() + .updated(true) + .allNotifications(true) + .newStatus(NotificationStatus.READ) + .build(); + onNotificationUpdate(tenantId, recipientId, update); + } + } + + @Override + public void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) { + Notification notification = notificationService.findNotificationById(tenantId, notificationId); + boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId); + if (deleted) { + NotificationUpdate update = NotificationUpdate.builder() + .deleted(true) + .notification(notification) + .build(); + onNotificationUpdate(tenantId, recipientId, update); + } + } + + @Override + public Set getAvailableDeliveryMethods(TenantId tenantId) { + return channels.values().stream() + .filter(channel -> channel.check(tenantId)) + .map(NotificationChannel::getDeliveryMethod) + .collect(Collectors.toSet()); + } + + @Override + public boolean check(TenantId tenantId) { + return true; + } + + @Override + public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { + log.debug("Deleting notification request {}", notificationRequestId); + NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId); + notificationRequestService.deleteNotificationRequest(tenantId, notificationRequestId); + + if (notificationRequest.isSent()) { + // TODO: no need to send request update for other than PLATFORM_USERS target type + onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() + .notificationRequestId(notificationRequestId) + .deleted(true) + .build()); + } else if (notificationRequest.isScheduled()) { + // TODO: just forward to scheduler service + clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); + } + } + + private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) { + TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setRequestIdMSB(notificationRequestId.getId().getMostSignificantBits()) + .setRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits()) + .setTs(System.currentTimeMillis()); + TransportProtos.ToCoreMsg toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setNotificationSchedulerServiceMsg(msg) + .build(); + clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); + } + + private ListenableFuture onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate update) { + log.trace("Submitting notification update for recipient {}: {}", recipientId, update); + return Futures.submit(() -> { + forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { + subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY); + }, () -> TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update)); + }, wsCallBackExecutor); + } + + private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { + log.trace("Submitting notification request update: {}", update); + wsCallBackExecutor.submit(() -> { + TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); + Set coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE)); + for (String serviceId : coreServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); + producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), notificationRequestUpdateProto), null); + } + }); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.WEB; + } + + @Override + protected String getExecutorPrefix() { + return "notification"; + } + + @Autowired + public void setChannels(List channels, NotificationCenter webNotificationChannel) { + this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c)); + this.channels.put(NotificationDeliveryMethod.WEB, (NotificationChannel) webNotificationChannel); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java new file mode 100644 index 0000000000..01591003ad --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -0,0 +1,175 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.executors.NotificationExecutorService; +import org.thingsboard.server.service.partition.AbstractPartitionBasedService; + +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@TbCoreComponent +@Service +@RequiredArgsConstructor +@Slf4j +@SuppressWarnings("UnstableApiUsage") +public class DefaultNotificationSchedulerService extends AbstractPartitionBasedService implements NotificationSchedulerService { + + private final NotificationCenter notificationCenter; + private final NotificationRequestService notificationRequestService; + private final SchedulerComponent scheduler; + private final NotificationExecutorService notificationExecutor; + + private final Map scheduledNotificationRequests = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + super.init(); + } + + @Override + protected Map>> onAddedPartitions(Set addedPartitions) { + PageDataIterable notificationRequests = new PageDataIterable<>(pageLink -> { + return notificationRequestService.findScheduledNotificationRequests(pageLink); + }, 1000); + for (NotificationRequest notificationRequest : notificationRequests) { + TopicPartitionInfo requestPartition = partitionService.resolve(ServiceType.TB_CORE, notificationRequest.getTenantId(), notificationRequest.getId()); + if (addedPartitions.contains(requestPartition)) { + partitionedEntities.computeIfAbsent(requestPartition, k -> ConcurrentHashMap.newKeySet()).add(notificationRequest.getId()); + if (!scheduledNotificationRequests.containsKey(notificationRequest.getId())) { + scheduleNotificationRequest(notificationRequest.getTenantId(), notificationRequest, notificationRequest.getCreatedTime()); + } + } + } + return Collections.emptyMap(); + } + + @Override + public void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs) { + NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId); + scheduleNotificationRequest(tenantId, notificationRequest, requestTs); + } + + private void scheduleNotificationRequest(TenantId tenantId, NotificationRequest request, long requestTs) { + int delayInSec = Optional.ofNullable(request) + .map(NotificationRequest::getAdditionalConfig) + .map(NotificationRequestConfig::getSendingDelayInSec) + .orElse(0); + if (delayInSec <= 0) return; + long delayInMs = TimeUnit.SECONDS.toMillis(delayInSec) - (System.currentTimeMillis() - requestTs); + if (delayInMs < 0) { + delayInMs = 0; + } + + ScheduledFuture scheduledTask = scheduler.schedule(() -> { + NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, request.getId()); + if (notificationRequest == null) return; + + notificationExecutor.executeAsync(() -> { + try { + notificationCenter.processNotificationRequest(tenantId, notificationRequest, null); + } catch (Exception e) { + log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e); + NotificationRequestStats stats = new NotificationRequestStats(); + stats.setError(e.getMessage()); + notificationRequestService.updateNotificationRequest(tenantId, request.getId(), NotificationRequestStatus.SENT, stats); + } + }); + scheduledNotificationRequests.remove(notificationRequest.getId()); + }, delayInMs, TimeUnit.MILLISECONDS); + scheduledNotificationRequests.put(request.getId(), new ScheduledRequestMetadata(tenantId, scheduledTask)); + } + + @EventListener(ComponentLifecycleMsg.class) + public void handleComponentLifecycleEvent(ComponentLifecycleMsg event) { + if (event.getEvent() == ComponentLifecycleEvent.DELETED) { + EntityId entityId = event.getEntityId(); + switch (entityId.getEntityType()) { + case NOTIFICATION_REQUEST: + cancelAndRemove((NotificationRequestId) entityId); + break; + case TENANT: + Set toCancel = new HashSet<>(); + scheduledNotificationRequests.forEach((notificationRequestId, scheduledRequestMetadata) -> { + if (scheduledRequestMetadata.getTenantId().equals(entityId)) { + toCancel.add(notificationRequestId); + } + }); + toCancel.forEach(this::cancelAndRemove); + break; + } + } + } + + @Override + protected void cleanupEntityOnPartitionRemoval(NotificationRequestId notificationRequestId) { + cancelAndRemove(notificationRequestId); + } + + private void cancelAndRemove(NotificationRequestId notificationRequestId) { + ScheduledRequestMetadata md = scheduledNotificationRequests.remove(notificationRequestId); + if (md != null) { + md.getFuture().cancel(false); + } + } + + @Override + protected String getServiceName() { + return "Notifications scheduler"; + } + + @Override + protected String getSchedulerExecutorName() { + return "notifications-scheduler"; + } + + @Data + private static class ScheduledRequestMetadata { + private final TenantId tenantId; + private final ScheduledFuture future; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java new file mode 100644 index 0000000000..8f9b7d048b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -0,0 +1,137 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import com.google.common.base.Strings; +import lombok.Builder; +import lombok.Getter; +import org.apache.commons.collections4.MapUtils; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; +import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.HasSubject; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.util.TemplateUtils; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +@SuppressWarnings("unchecked") +public class NotificationProcessingContext { + + @Getter + private final TenantId tenantId; + private final NotificationSettings settings; + @Getter + private final NotificationRequest request; + + @Getter + private final NotificationTemplate notificationTemplate; + private final Map templates; + @Getter + private Set deliveryMethods; + @Getter + private final NotificationRequestStats stats; + + + @Builder + public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) { + this.tenantId = tenantId; + this.request = request; + this.settings = settings; + this.notificationTemplate = template; + this.templates = new EnumMap<>(NotificationDeliveryMethod.class); + this.stats = new NotificationRequestStats(); + init(); + } + + private void init() { + NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration(); + templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { + if (template.isEnabled()) { + template = processTemplate(template, null); // processing template with immutable params + templates.put(deliveryMethod, template); + } + }); + deliveryMethods = templates.keySet(); + } + + public C getDeliveryMethodConfig(NotificationDeliveryMethod deliveryMethod) { + return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); + } + + public T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) { + T template = (T) templates.get(deliveryMethod); + Map additionalTemplateContext = null; + if (recipient != null) { + additionalTemplateContext = createTemplateContextForRecipient(recipient); + } + if (MapUtils.isNotEmpty(additionalTemplateContext) && template.containsAny(additionalTemplateContext.keySet().toArray(String[]::new))) { + template = processTemplate(template, additionalTemplateContext); + } + return template; + } + + private T processTemplate(T template, Map additionalTemplateContext) { + Map templateContext = new HashMap<>(); + if (request.getInfo() != null) { + templateContext.putAll(request.getInfo().getTemplateData()); + } + if (additionalTemplateContext != null) { + templateContext.putAll(additionalTemplateContext); + } + if (templateContext.isEmpty()) return template; + + template = (T) template.copy(); + template.setBody(TemplateUtils.processTemplate(template.getBody(), templateContext)); + if (template instanceof HasSubject) { + String subject = ((HasSubject) template).getSubject(); + ((HasSubject) template).setSubject(TemplateUtils.processTemplate(subject, templateContext)); + } + if (template instanceof WebDeliveryMethodNotificationTemplate) { + WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template; + String buttonText = webNotificationTemplate.getButtonText(); + if (isNotEmpty(buttonText)) { + webNotificationTemplate.setButtonText(TemplateUtils.processTemplate(buttonText, templateContext)); + } + String buttonLink = webNotificationTemplate.getButtonLink(); + if (isNotEmpty(buttonLink)) { + webNotificationTemplate.setButtonLink(TemplateUtils.processTemplate(buttonLink, templateContext)); + } + } + return template; + } + + private Map createTemplateContextForRecipient(NotificationRecipient recipient) { + return Map.of( + "recipientEmail", Strings.nullToEmpty(recipient.getEmail()), + "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()), + "recipientLastName", Strings.nullToEmpty(recipient.getLastName()) + ); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java new file mode 100644 index 0000000000..c02d96345d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification; + +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.TenantId; + +public interface NotificationSchedulerService { + + void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java new file mode 100644 index 0000000000..61e4c528dc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.TbEmail; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; +import org.thingsboard.server.service.mail.MailExecutorService; +import org.thingsboard.server.service.notification.NotificationProcessingContext; + +@Component +@RequiredArgsConstructor +public class EmailNotificationChannel implements NotificationChannel { + + private final MailService mailService; + private final MailExecutorService executor; + + @Override + public ListenableFuture sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { + return executor.submit(() -> { + mailService.send(recipient.getTenantId(), null, TbEmail.builder() + .to(recipient.getEmail()) + .subject(processedTemplate.getSubject()) + .body(processedTemplate.getBody()) + .html(true) + .build()); + return null; + }); + } + + @Override + public boolean check(TenantId tenantId) { + try { + mailService.testConnection(tenantId); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.EMAIL; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java new file mode 100644 index 0000000000..e66a91725c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.service.notification.NotificationProcessingContext; + +public interface NotificationChannel { + + ListenableFuture sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx); + + boolean check(TenantId tenantId); + + NotificationDeliveryMethod getDeliveryMethod(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java new file mode 100644 index 0000000000..7ff48d4cb9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; +import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; +import org.thingsboard.server.service.executors.ExternalCallExecutorService; + +@Component +@RequiredArgsConstructor +public class SlackNotificationChannel implements NotificationChannel { + + private final SlackService slackService; + private final NotificationSettingsService notificationSettingsService; + private final ExternalCallExecutorService executor; + + @Override + public ListenableFuture sendNotification(SlackConversation conversation, SlackDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { + SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK); + return executor.submit(() -> { + slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), processedTemplate.getBody()); + return null; + }); + } + + @Override + public boolean check(TenantId tenantId) { + NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId); + return notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.SLACK; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java new file mode 100644 index 0000000000..8f3864d345 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; +import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.service.sms.SmsExecutorService; + +@Component +@RequiredArgsConstructor +public class SmsNotificationChannel implements NotificationChannel { + + private final SmsService smsService; + private final SmsExecutorService executor; + + @Override + public ListenableFuture sendNotification(User recipient, SmsDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { + String phone = recipient.getPhone(); + if (StringUtils.isBlank(phone)) { + return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number")); + } + + return executor.submit(() -> { + smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, processedTemplate.getBody()); + return null; + }); + } + + @Override + public boolean check(TenantId tenantId) { + return smsService.isConfigured(tenantId); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.SMS; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java new file mode 100644 index 0000000000..18d8198087 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java @@ -0,0 +1,195 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStatus; +import org.thingsboard.server.common.data.notification.info.NotificationInfo; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.dao.notification.NotificationRequestService; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.service.executors.NotificationExecutorService; +import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; +import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor; + +import java.util.Collection; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@SuppressWarnings({"rawtypes", "unchecked"}) +public class DefaultNotificationRuleProcessor implements NotificationRuleProcessor { + + private final NotificationRuleService notificationRuleService; + private final NotificationRequestService notificationRequestService; + @Autowired @Lazy + private NotificationCenter notificationCenter; + private final NotificationExecutorService notificationExecutor; + + private final Map triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class); + + @Override + public void process(NotificationRuleTrigger trigger) { + NotificationRuleTriggerType triggerType = trigger.getType(); + if (triggerType == null) return; + TenantId tenantId = triggerType.isTenantLevel() ? trigger.getTenantId() : TenantId.SYS_TENANT_ID; + + List rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType); + for (NotificationRule rule : rules) { + notificationExecutor.submit(() -> { + try { + processNotificationRule(rule, trigger); + } catch (Throwable e) { + log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e); + } + }); + } + } + + private void processNotificationRule(NotificationRule rule, NotificationRuleTrigger trigger) { + NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig(); + log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); + + if (matchesClearRule(trigger, triggerConfig)) { + List notificationRequests = findAlreadySentNotificationRequests(rule, trigger); + if (notificationRequests.isEmpty()) { + return; + } + + List targets = notificationRequests.stream() + .filter(NotificationRequest::isSent) + .flatMap(notificationRequest -> notificationRequest.getTargets().stream()) + .distinct().collect(Collectors.toList()); + NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); + submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0); + + notificationRequests.forEach(notificationRequest -> { + if (notificationRequest.isScheduled()) { + notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId()); + } + }); + return; + } + + if (matchesFilter(trigger, triggerConfig)) { + NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); + rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> { + submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay); + }); + } + } + + private List findAlreadySentNotificationRequests(NotificationRule rule, NotificationRuleTrigger trigger) { + return notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId()); + } + + private void submitNotificationRequest(List targets, NotificationRule rule, + EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) { + NotificationRequestConfig config = new NotificationRequestConfig(); + if (delayInSec > 0) { + config.setSendingDelayInSec(delayInSec); + } + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(rule.getTenantId()) + .targets(targets) + .templateId(rule.getTemplateId()) + .additionalConfig(config) + .info(notificationInfo) + .ruleId(rule.getId()) + .originatorEntityId(originatorEntityId) + .build(); + notificationExecutor.submit(() -> { + try { + log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets); + notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest, null); + } catch (Exception e) { + log.error("Failed to process notification request for tenant {} for rule {}", rule.getTenantId(), rule.getId(), e); + } + }); + } + + private boolean matchesFilter(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { + return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(trigger, triggerConfig); + } + + private boolean matchesClearRule(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { + return triggerProcessors.get(triggerConfig.getTriggerType()).matchesClearRule(trigger, triggerConfig); + } + + private NotificationInfo constructNotificationInfo(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { + return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger); + } + + @EventListener(ComponentLifecycleMsg.class) + public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) { + if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED || + componentLifecycleMsg.getEntityId().getEntityType() != EntityType.NOTIFICATION_RULE) { + return; + } + + TenantId tenantId = componentLifecycleMsg.getTenantId(); + NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId(); + notificationExecutor.submit(() -> { + List scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId); + for (NotificationRequestId notificationRequestId : scheduledForRule) { + notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId); + } + }); + } + + @Autowired + public void setTriggerProcessors(Collection processors) { + Map ruleEngineMsgTypeToTriggerType = new HashMap<>(); + processors.forEach(processor -> { + triggerProcessors.put(processor.getTriggerType(), processor); + if (processor instanceof RuleEngineMsgNotificationRuleTriggerProcessor) { + Set supportedMsgTypes = ((RuleEngineMsgNotificationRuleTriggerProcessor) processor).getSupportedMsgTypes(); + supportedMsgTypes.forEach(supportedMsgType -> { + ruleEngineMsgTypeToTriggerType.put(supportedMsgType, processor.getTriggerType()); + }); + } + }); + RuleEngineMsgTrigger.msgTypeToTriggerType = ruleEngineMsgTypeToTriggerType; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java new file mode 100644 index 0000000000..54c53a54d2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java @@ -0,0 +1,84 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmAssignee; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; +import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig.Action; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; + +import java.util.Set; + +import static org.apache.commons.collections.CollectionUtils.isEmpty; + +@Service +public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(RuleEngineMsgTrigger trigger, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) { + Action action = trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGN) ? Action.ASSIGNED : Action.UNASSIGNED; + if (!triggerConfig.getNotifyOn().contains(action)) { + return false; + } + Alarm alarm = JacksonUtil.fromString(trigger.getMsg().getData(), Alarm.class); + return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && + (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && + (isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm)); + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) { + AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class); + AlarmAssignee assignee = alarmInfo.getAssignee(); + return AlarmAssignmentNotificationInfo.builder() + .action(trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGN) ? "assigned" : "unassigned") + .assigneeFirstName(assignee != null ? assignee.getFirstName() : null) + .assigneeLastName(assignee != null ? assignee.getLastName() : null) + .assigneeEmail(assignee != null ? assignee.getEmail() : null) + .assigneeId(assignee != null ? assignee.getId() : null) + .userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) + .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) + .userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) + .alarmId(alarmInfo.getUuidId()) + .alarmType(alarmInfo.getType()) + .alarmOriginator(alarmInfo.getOriginator()) + .alarmOriginatorName(alarmInfo.getOriginatorName()) + .alarmSeverity(alarmInfo.getSeverity()) + .alarmStatus(alarmInfo.getStatus()) + .alarmCustomerId(alarmInfo.getCustomerId()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM_ASSIGNMENT; + } + + @Override + public Set getSupportedMsgTypes() { + return Set.of(DataConstants.ALARM_ASSIGN, DataConstants.ALARM_UNASSIGN); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java new file mode 100644 index 0000000000..f10e395a78 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; +import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; + +import java.util.Set; + +import static org.apache.commons.collections.CollectionUtils.isEmpty; + +@Service +public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(RuleEngineMsgTrigger trigger, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { + TbMsg msg = trigger.getMsg(); + if (msg.getMetaData().getValue("comment") == null) { + return false; + } + if (msg.getType().equals(DataConstants.COMMENT_UPDATED) && !triggerConfig.isNotifyOnCommentUpdate()) { + return false; + } + if (triggerConfig.isOnlyUserComments()) { + AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class); + if (comment.getType() == AlarmCommentType.SYSTEM) { + return false; + } + } + Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); + return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && + (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && + (isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm)); + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) { + TbMsg msg = trigger.getMsg(); + AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class); + AlarmInfo alarmInfo = JacksonUtil.fromString(msg.getData(), AlarmInfo.class); + return AlarmCommentNotificationInfo.builder() + .comment(comment.getComment().get("text").asText()) + .action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated") + .userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) + .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) + .userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) + .alarmId(alarmInfo.getUuidId()) + .alarmType(alarmInfo.getType()) + .alarmOriginator(alarmInfo.getOriginator()) + .alarmOriginatorName(alarmInfo.getOriginatorName()) + .alarmSeverity(alarmInfo.getSeverity()) + .alarmStatus(alarmInfo.getStatus()) + .alarmCustomerId(alarmInfo.getCustomerId()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM_COMMENT; + } + + @Override + public Set getSupportedMsgTypes() { + return Set.of(DataConstants.COMMENT_CREATED, DataConstants.COMMENT_UPDATED); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java new file mode 100644 index 0000000000..c8ebf73304 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java @@ -0,0 +1,120 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; +import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; +import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger; + +import static org.apache.commons.collections.CollectionUtils.isEmpty; +import static org.apache.commons.collections.CollectionUtils.isNotEmpty; + +@Service +public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(AlarmTrigger trigger, AlarmNotificationRuleTriggerConfig triggerConfig) { + AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate(); + Alarm alarm = alarmUpdate.getAlarm(); + if (!typeMatches(alarm, triggerConfig)) { + return false; + } + + if (alarmUpdate.isCreated()) { + if (triggerConfig.getNotifyOn().contains(AlarmAction.CREATED)) { + return severityMatches(alarm, triggerConfig); + } + } else if (alarmUpdate.isSeverityChanged()) { + if (triggerConfig.getNotifyOn().contains(AlarmAction.SEVERITY_CHANGED)) { + return severityMatches(alarmUpdate.getOld(), triggerConfig) || severityMatches(alarm, triggerConfig); + } else { + // if we haven't yet sent notification about the alarm + return !severityMatches(alarmUpdate.getOld(), triggerConfig) && severityMatches(alarm, triggerConfig); + } + } else if (alarmUpdate.isAcknowledged()) { + if (triggerConfig.getNotifyOn().contains(AlarmAction.ACKNOWLEDGED)) { + return severityMatches(alarm, triggerConfig); + } + } else if (alarmUpdate.isCleared()) { + if (triggerConfig.getNotifyOn().contains(AlarmAction.CLEARED)) { + return severityMatches(alarm, triggerConfig); + } + } + return false; + } + + @Override + public boolean matchesClearRule(AlarmTrigger trigger, AlarmNotificationRuleTriggerConfig triggerConfig) { + AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate(); + Alarm alarm = alarmUpdate.getAlarm(); + if (!typeMatches(alarm, triggerConfig)) { + return false; + } + if (alarmUpdate.isDeleted()) { + return true; + } + ClearRule clearRule = triggerConfig.getClearRule(); + if (clearRule != null) { + if (isNotEmpty(clearRule.getAlarmStatuses())) { + return AlarmStatusFilter.from(clearRule.getAlarmStatuses()).matches(alarm); + } + } + return false; + } + + private boolean severityMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { + return isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity()); + } + + private boolean typeMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { + return isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType()); + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(AlarmTrigger trigger) { + AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate(); + AlarmInfo alarmInfo = alarmUpdate.getAlarm(); + return AlarmNotificationInfo.builder() + .alarmId(alarmInfo.getUuidId()) + .alarmType(alarmInfo.getType()) + .action(alarmUpdate.isCreated() ? "created" : + alarmUpdate.isSeverityChanged() ? "severity changed" : + alarmUpdate.isAcknowledged() ? "acknowledged" : + alarmUpdate.isCleared() ? "cleared" : + alarmUpdate.isDeleted() ? "deleted" : null) + .alarmOriginator(alarmInfo.getOriginator()) + .alarmOriginatorName(alarmInfo.getOriginatorName()) + .alarmSeverity(alarmInfo.getSeverity()) + .alarmStatus(alarmInfo.getStatus()) + .alarmCustomerId(alarmInfo.getCustomerId()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java new file mode 100644 index 0000000000..574d7e06e7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.notification.info.ApiUsageLimitNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger; +import org.thingsboard.server.dao.tenant.TenantService; + +import static org.apache.commons.collections.CollectionUtils.isEmpty; + +@Service +@RequiredArgsConstructor +public class ApiUsageLimitTriggerProcessor implements NotificationRuleTriggerProcessor { + + private final TenantService tenantService; + + @Override + public boolean matchesFilter(ApiUsageLimitTrigger trigger, ApiUsageLimitNotificationRuleTriggerConfig triggerConfig) { + return (isEmpty(triggerConfig.getApiFeatures()) || triggerConfig.getApiFeatures().contains(trigger.getState().getApiFeature())) && + (isEmpty(triggerConfig.getNotifyOn()) || triggerConfig.getNotifyOn().contains(trigger.getStatus())); + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(ApiUsageLimitTrigger trigger) { + return ApiUsageLimitNotificationInfo.builder() + .feature(trigger.getState().getApiFeature()) + .recordKey(trigger.getState().getKey()) + .status(trigger.getStatus()) + .limit(trigger.getState().getThresholdAsString()) + .currentValue(trigger.getState().getValueAsString()) + .tenantId(trigger.getTenantId()) + .tenantName(tenantService.findTenantById(trigger.getTenantId()).getName()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.API_USAGE_LIMIT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java new file mode 100644 index 0000000000..5dee2b69b0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; + +import java.util.Set; + +@Service +@RequiredArgsConstructor +public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor { + + private final TbDeviceProfileCache deviceProfileCache; + + @Override + public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceActivityNotificationRuleTriggerConfig triggerConfig) { + DeviceEvent event = trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? DeviceEvent.ACTIVE : DeviceEvent.INACTIVE; + if (!triggerConfig.getNotifyOn().contains(event)) { + return false; + } + DeviceId deviceId = (DeviceId) trigger.getMsg().getOriginator(); + if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) { + return triggerConfig.getDevices().contains(deviceId.getId()); + } else if (CollectionUtils.isNotEmpty(triggerConfig.getDeviceProfiles())) { + DeviceProfile deviceProfile = deviceProfileCache.get(TenantId.SYS_TENANT_ID, deviceId); + return deviceProfile != null && triggerConfig.getDeviceProfiles().contains(deviceProfile.getUuidId()); + } else { + return true; + } + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) { + TbMsg msg = trigger.getMsg(); + return DeviceActivityNotificationInfo.builder() + .eventType(trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? "active" : "inactive") + .deviceId(msg.getOriginator().getId()) + .deviceName(msg.getMetaData().getValue("deviceName")) + .deviceType(msg.getMetaData().getValue("deviceType")) + .deviceLabel(msg.getMetaData().getValue("deviceLabel")) + .deviceCustomerId(msg.getCustomerId()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.DEVICE_ACTIVITY; + } + + @Override + public Set getSupportedMsgTypes() { + return Set.of(DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java new file mode 100644 index 0000000000..da2ec7042e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger; +import org.thingsboard.server.dao.tenant.TenantService; + +import static org.apache.commons.collections.CollectionUtils.isNotEmpty; + +@Service +@RequiredArgsConstructor +public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerProcessor { + + private final TenantService tenantService; + + @Override + public boolean matchesFilter(EntitiesLimitTrigger trigger, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) { + if (isNotEmpty(triggerConfig.getEntityTypes()) && !triggerConfig.getEntityTypes().contains(trigger.getEntityType())) { + return false; + } + return (int) (trigger.getLimit() * triggerConfig.getThreshold()) == trigger.getCurrentCount(); // strict comparing not to send notification on each new entity + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger) { + return EntitiesLimitNotificationInfo.builder() + .entityType(trigger.getEntityType()) + .currentCount(trigger.getCurrentCount()) + .limit(trigger.getLimit()) + .percents((int) (((float)trigger.getCurrentCount() / trigger.getLimit()) * 100)) + .tenantId(trigger.getTenantId()) + .tenantName(tenantService.findTenantById(trigger.getTenantId()).getName()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ENTITIES_LIMIT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java new file mode 100644 index 0000000000..8b8a90e10e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import static org.apache.commons.collections.CollectionUtils.isEmpty; + +@Service +public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) { + String msgType = trigger.getMsg().getType(); + if (msgType.equals(DataConstants.ENTITY_CREATED)) { + if (!triggerConfig.isCreated()) { + return false; + } + } else if (msgType.equals(DataConstants.ENTITY_UPDATED)) { + if (!triggerConfig.isUpdated()) { + return false; + } + } else if (msgType.equals(DataConstants.ENTITY_DELETED)) { + if (!triggerConfig.isDeleted()) { + return false; + } + } else { + return false; + } + return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg())); + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) { + TbMsg msg = trigger.getMsg(); + String msgType = msg.getType(); + ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED : + msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED : + msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null; + return EntityActionNotificationInfo.builder() + .entityId(msg.getOriginator()) + .entityName(msg.getMetaData().getValue("entityName")) + .actionType(actionType) + .userId(UUID.fromString(msg.getMetaData().getValue("userId"))) + .userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) + .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) + .userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) + .entityCustomerId(msg.getCustomerId()) + .build(); + } + + private static EntityType getEntityType(TbMsg msg) { + return Optional.ofNullable(msg.getMetaData().getValue("entityType")) + .map(EntityType::valueOf).orElse(null); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ENTITY_ACTION; + } + + @Override + public Set getSupportedMsgTypes() { + return Set.of(DataConstants.ENTITY_CREATED, DataConstants.ENTITY_UPDATED, DataConstants.ENTITY_DELETED); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java new file mode 100644 index 0000000000..9706862ed4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.queue.discovery.PartitionService; + +@Service +@RequiredArgsConstructor +public class NewPlatformVersionTriggerProcessor implements NotificationRuleTriggerProcessor { + + private final PartitionService partitionService; + + @Override + public boolean matchesFilter(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) { + // todo: don't send repetitive notification after platform restart? + if (!partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition()) { + return false; + } + return trigger.getMessage().isUpdateAvailable(); + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) { + return NewPlatformVersionNotificationInfo.builder() + .message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {})) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.NEW_PLATFORM_VERSION; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java new file mode 100644 index 0000000000..4d5228e32c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; + +public interface NotificationRuleTriggerProcessor { + + boolean matchesFilter(T trigger, C triggerConfig); + + default boolean matchesClearRule(T trigger, C triggerConfig) { + return false; + } + + RuleOriginatedNotificationInfo constructNotificationInfo(T trigger); + + NotificationRuleTriggerType getTriggerType(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java new file mode 100644 index 0000000000..a8b0b014a0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java @@ -0,0 +1,107 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.notification.info.RuleEngineComponentLifecycleEventNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger; +import org.thingsboard.server.queue.discovery.PartitionService; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Set; + +@Service +@RequiredArgsConstructor +public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor { + + private final PartitionService partitionService; + + @Override + public boolean matchesFilter(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { + if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) { + if (!triggerConfig.getRuleChains().contains(trigger.getRuleChainId().getId())) { + return false; + } + } + if (!partitionService.resolve(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId()).isMyPartition()) { + return false; + } + + EntityType componentType = trigger.getComponentId().getEntityType(); + Set trackedEvents; + boolean onlyFailures; + if (componentType == EntityType.RULE_CHAIN) { + trackedEvents = triggerConfig.getRuleChainEvents(); + onlyFailures = triggerConfig.isOnlyRuleChainLifecycleFailures(); + } else if (componentType == EntityType.RULE_NODE && triggerConfig.isTrackRuleNodeEvents()) { + trackedEvents = triggerConfig.getRuleNodeEvents(); + onlyFailures = triggerConfig.isOnlyRuleNodeLifecycleFailures(); + } else { + return false; + } + if (CollectionUtils.isEmpty(trackedEvents)) { + trackedEvents = Set.of(ComponentLifecycleEvent.STARTED, ComponentLifecycleEvent.UPDATED, ComponentLifecycleEvent.STOPPED); + } + + if (!trackedEvents.contains(trigger.getEventType())) { + return false; + } + if (onlyFailures) { + return trigger.getError() != null; + } + return true; + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger) { + return RuleEngineComponentLifecycleEventNotificationInfo.builder() + .ruleChainId(trigger.getRuleChainId()) + .ruleChainName(trigger.getRuleChainName()) + .componentId(trigger.getComponentId()) + .componentName(trigger.getComponentName()) + .action(trigger.getEventType() == ComponentLifecycleEvent.STARTED ? "start" : + trigger.getEventType() == ComponentLifecycleEvent.UPDATED ? "update" : + trigger.getEventType() == ComponentLifecycleEvent.STOPPED ? "stop" : null) + .eventType(trigger.getEventType()) + .error(getErrorMsg(trigger.getError())) + .build(); + } + + private String getErrorMsg(Throwable error) { + if (error == null) return null; + + StringWriter sw = new StringWriter(); + error.printStackTrace(new PrintWriter(sw)); + return StringUtils.abbreviate(ExceptionUtils.getStackTrace(error), 200); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java new file mode 100644 index 0000000000..ac8f692f02 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; +import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; + +import java.util.Set; + +public interface RuleEngineMsgNotificationRuleTriggerProcessor extends NotificationRuleTriggerProcessor { + + Set getSupportedMsgTypes(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 17ea56de2f..457e89b3fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java index d6915b3907..aaf8b08445 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java b/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java index 305696662b..bda4108ea7 100644 --- a/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java +++ b/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +20,17 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,6 +49,8 @@ public abstract class AbstractPartitionBasedService extends protected final ConcurrentMap>> partitionedFetchTasks = new ConcurrentHashMap<>(); final Queue> subscribeQueue = new ConcurrentLinkedQueue<>(); + @Autowired + protected PartitionService partitionService; protected ListeningScheduledExecutorService scheduledExecutor; abstract protected String getServiceName(); diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java index 23d0e1b616..6d6a6b88b9 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java index 8ec29fff81..2b5a641cd5 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java index 6c332e63ff..e9b104168e 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java index 03fc3e36ce..536eaab883 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index d2a6369c51..d0cd58aa90 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.query.AlarmCountQuery; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; @@ -205,6 +206,11 @@ public class DefaultEntityQueryService implements EntityQueryService { } } + @Override + public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) { + return alarmService.countAlarmsByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); + } + private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) { EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); EntityDataSortOrder entitiesSortOrder; diff --git a/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java index a2f1efb1e4..cb936de31c 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.query.AlarmCountQuery; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.EntityCountQuery; @@ -34,6 +35,8 @@ public interface EntityQueryService { PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query); + long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query); + DeferredResult getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query, boolean isTimeseries, boolean isAttributes); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultQueueRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultQueueRoutingInfoService.java index d7a333a090..24c55d9efe 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultQueueRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultQueueRoutingInfoService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index e2779ba28b..78ea88b32d 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 931acf908c..ad2a5baebe 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +18,29 @@ package org.thingsboard.server.service.queue; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; -import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; @@ -62,9 +66,11 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.AfterStartUp; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; +import org.thingsboard.server.service.notification.NotificationSchedulerService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -72,12 +78,16 @@ import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.sync.vc.GitVersionControlQueueService; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -120,6 +130,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> usageStatsConsumer; private final TbQueueConsumer> firmwareStatesConsumer; @@ -145,8 +157,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService jwtSettingsService) { - super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); + ApplicationEventPublisher eventPublisher, + Optional jwtSettingsService, + NotificationSchedulerService notificationSchedulerService, + NotificationRuleProcessor notificationRuleProcessor) { + super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); @@ -159,6 +174,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService notificationRuleTrigger = encodingService.decode(toCoreMsg.getNotificationRuleProcessorMsg().getTrigger().toByteArray()); + notificationRuleTrigger.ifPresent(notificationRuleProcessor::process); } } catch (Throwable e) { log.warn("[{}] Failed to process message: {}", id, msg, e); @@ -335,6 +359,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> nfConsumer; protected final Optional jwtSettingsService; @@ -83,7 +85,8 @@ public abstract class AbstractConsumerService> nfConsumer, Optional jwtSettingsService) { + PartitionService partitionService, ApplicationEventPublisher eventPublisher, + TbQueueConsumer> nfConsumer, Optional jwtSettingsService) { this.actorContext = actorContext; this.encodingService = encodingService; this.tenantProfileCache = tenantProfileCache; @@ -91,6 +94,7 @@ public abstract class AbstractConsumerService objectModels = - ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); - if (objectModels.size() == 0) { - return null; - } else { - ObjectModel obj = objectModels.get(0); - LwM2mObject lwM2mObject = new LwM2mObject(); - lwM2mObject.setId(obj.id); - lwM2mObject.setKeyId(resource.getResourceKey()); - lwM2mObject.setName(obj.name); - lwM2mObject.setMultiple(obj.multiple); - lwM2mObject.setMandatory(obj.mandatory); - LwM2mInstance instance = new LwM2mInstance(); - instance.setId(0); - List resources = new ArrayList<>(); - obj.resources.forEach((k, v) -> { - if (isSave) { - LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); - resources.add(lwM2MResourceObserve); - } else if (v.operations.isReadable()) { - LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); - resources.add(lwM2MResourceObserve); - } - }); - if (isSave || resources.size() > 0) { - instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); - lwM2mObject.setInstances(new LwM2mInstance[]{instance}); - return lwM2mObject; - } else { - return null; - } - } - } catch (IOException | InvalidDDFFileException e) { - log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e); - return null; - } - } - @Override public TbResource save(TbResource tbResource, User user) throws ThingsboardException { ActionType actionType = tbResource.getId() == null ? ActionType.ADDED : ActionType.UPDATED; @@ -215,35 +165,11 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements throw new DataValidationException("Resource data should be specified!"); } if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { - try { - List objectModels = - ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); - if (!objectModels.isEmpty()) { - ObjectModel objectModel = objectModels.get(0); - - String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version; - String name = objectModel.name; - resource.setResourceKey(resourceKey); - if (resource.getId() == null) { - resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version); - } - resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); - } else { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); - } - } catch (InvalidDDFFileException e) { - log.error("Failed to parse file {}", resource.getFileName(), e); - throw new DataValidationException("Failed to parse file " + resource.getFileName()); - } catch (IOException e) { - throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); - } - if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); - } + toLwm2mResource(resource); } else { resource.setResourceKey(resource.getFileName()); } - return resourceService.saveResource(resource); } } + diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 6fd95660fc..024d391482 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index fe70f441f5..4302854715 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java index c0f6cc750f..47d70d3330 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java index 128a1a5b84..6f4d2af36b 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java b/application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java index 5f3f01756d..cdb6e5586a 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java index 9173d4455c..c0b0c64b2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java index 5443de70fc..9b81f4c6de 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java index 47fdab1658..1cd4cb198f 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java index 7464de6d03..1d460dca82 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java index 5c86292933..a0f6418d27 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 28a4e1e854..c2e425e14a 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java index a8c3fed780..02b79544f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 9b5a678d35..8a8c3213a4 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java index 3a88bb65a4..c8af61addf 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java index 08b2d5e245..0718c7383d 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,7 +124,7 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine(); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 0f32e31cf6..1dae050f58 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; +import org.thingsboard.server.exception.ToErrorResponseEntity; import javax.annotation.Nullable; import javax.annotation.PostConstruct; diff --git a/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java b/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java index ea90a7ae69..6c52d8e3ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java +++ b/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package org.thingsboard.server.service.security; import com.google.common.util.concurrent.FutureCallback; -import org.thingsboard.server.service.telemetry.exception.AccessDeniedException; -import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException; -import org.thingsboard.server.service.telemetry.exception.InternalErrorException; -import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; +import org.thingsboard.server.exception.AccessDeniedException; +import org.thingsboard.server.exception.EntityNotFoundException; +import org.thingsboard.server.exception.InternalErrorException; +import org.thingsboard.server.exception.UnauthorizedException; /** * Created by ashvayka on 31.03.18. diff --git a/application/src/main/java/org/thingsboard/server/service/security/ValidationResult.java b/application/src/main/java/org/thingsboard/server/service/security/ValidationResult.java index 7b91c7634a..7b22faee0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/ValidationResult.java +++ b/application/src/main/java/org/thingsboard/server/service/security/ValidationResult.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/ValidationResultCode.java b/application/src/main/java/org/thingsboard/server/service/security/ValidationResultCode.java index cf2bc3dfe2..95710165b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/ValidationResultCode.java +++ b/application/src/main/java/org/thingsboard/server/service/security/ValidationResultCode.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java index 563bc40b7b..3ed58951f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java index 74230d93ec..d38138afdd 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java index 1b854313c4..7cce7fce5c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/MfaAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/MfaAuthenticationToken.java index 8c70e69179..4d775b1e40 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/MfaAuthenticationToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/MfaAuthenticationToken.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java index ca76984c0a..5fedfdad99 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java index 02c1bf685a..20df639619 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java index 9ce2ae75fc..e0242126fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java index 164eb6df45..73cfe193e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java index 27549752e4..e75d3aa4d1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java index 7937641d16..5bf29acc75 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java index bdc5d403f1..3d849b1d3e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java index 7c5822792a..b4c4843d1b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java index 0b90cd8047..2f01decc73 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java index cb40379c22..7a458ef052 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java index 26cbc97bdd..b1976dce9a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java index 36beaecea3..d159c469c0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java index bf83179b78..dddb011fd5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,10 +36,10 @@ public class DefaultJwtSettingsValidator implements JwtSettingsValidator { if (StringUtils.isEmpty(jwtSettings.getTokenIssuer())) { throw new DataValidationException("JWT token issuer should be specified!"); } - if (Optional.ofNullable(jwtSettings.getRefreshTokenExpTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(15)) { + if (Optional.ofNullable(jwtSettings.getRefreshTokenExpTime()).orElse(0) < TimeUnit.MINUTES.toSeconds(15)) { throw new DataValidationException("JWT refresh token expiration time should be at least 15 minutes!"); } - if (Optional.ofNullable(jwtSettings.getTokenExpirationTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(1)) { + if (Optional.ofNullable(jwtSettings.getTokenExpirationTime()).orElse(0) < TimeUnit.MINUTES.toSeconds(1)) { throw new DataValidationException("JWT token expiration time should be at least 1 minute!"); } if (jwtSettings.getTokenExpirationTime() >= jwtSettings.getRefreshTokenExpTime()) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java index 6202e60c96..2081967677 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java index bf02be573c..7fc4306ec6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java index 06a82fc808..209ab22b78 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java index 0f36273f41..4231208850 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java index aabda27ec7..b1bdf82bbf 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java index ad2ecebd2c..4d85de7627 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java index c0e3200a4d..ead1fb7411 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java index 2522d3e41b..ff60a1c798 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java index ac8d76b52a..cb718462bf 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package org.thingsboard.server.service.security.auth.mfa.provider.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.CollectionsUtil; +import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java index 085b5a9f45..d762d00819 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java index b75e81be6f..96f382959d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java index 419a60b3b9..75e5a01441 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java index f634185f7a..0115b1acd9 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index ba0e4aa0b4..d27a4ecc83 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java index 5b2036ce26..9bbb3643e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java index e8e1011a47..393b652d63 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 42f44a712d..91de70e35c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java index e0f639b2f9..5807265c6c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 69f654997e..bc971e6104 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index d4d4d99005..0381e64298 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java index a6a5fa456f..d63f759dd3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index f84a2eef0a..23befd67c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java index 3c1ac4a5c6..3955cee0b3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index c12c6081f6..78725bc659 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 7de6c3fe65..36c6c61471 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java index 50f4424d2f..9211f06b98 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java index 43fbb3d1c4..78cf6b1f98 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java index 551868badb..8fdd443fe8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.java index 5f587e0974..1e01b4f975 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java index aedce8beae..bc80a22e1b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java index affcd44d99..36ce529e24 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java index b3ef88dbff..7d612fef25 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java index 5a1ee92aaf..e5d20ac546 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java index f6d9fd8666..90722d3c08 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java index 16e6f50c94..5e0118a6ad 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java index d126739d18..5bf0382aa4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java index 872217b430..a278972335 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java index 556ecd0e96..bcfb7bd17e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java +++ b/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java index ac95913070..87b7403a85 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java +++ b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/UserPasswordExpiredException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/UserPasswordExpiredException.java index 6dd8a344a5..7f682ff6fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/exception/UserPasswordExpiredException.java +++ b/application/src/main/java/org/thingsboard/server/service/security/exception/UserPasswordExpiredException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java index 50e918b0f4..aff95ea246 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java index 4a930403b4..4b21acef5e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java index 3b1563eb2d..cf3e722bbc 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java index 8489f6f17c..3869da9b02 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java index b7f480dfab..7ae64ef2b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.java b/application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.java index 4eafe1673c..7af0954d53 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java index f96e0bfc33..c74954c88a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index 71fd66231a..cde766443d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/OAuth2AppTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/OAuth2AppTokenFactory.java index bae8cbb1bb..fd2fc2079d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/OAuth2AppTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/OAuth2AppTokenFactory.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java index 86d66fb22c..f8a48d431f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java index 2b36ff63e8..0d98b5cd88 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java b/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java index 3c79e4ab00..a702626644 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index cdd84b0478..1a8f87713e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,6 +119,15 @@ public class CustomerUserPermissions extends AbstractPermissions { if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) { return false; } + + if (!user.getCustomerId().equals(userEntity.getCustomerId())) { + return false; + } + + if (Operation.READ.equals(operation)) { + return true; + } + return user.getId().equals(userId); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java index 64c7f056b6..301fb986c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java index d261107719..9873f16447 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java index 217c488c7e..a48f8ab3d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java index 9df6da09ab..53dd59603e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index a9484a2bd3..921f05b0d1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package org.thingsboard.server.service.security.permission; import org.thingsboard.server.common.data.EntityType; +import java.util.Collections; import java.util.Optional; +import java.util.Set; public enum Resource { ADMIN_SETTINGS(), @@ -43,25 +45,27 @@ public enum Resource { EDGE(EntityType.EDGE), RPC(EntityType.RPC), QUEUE(EntityType.QUEUE), - VERSION_CONTROL; + VERSION_CONTROL, + NOTIFICATION(EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_TEMPLATE, + EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE); - private final EntityType entityType; + private final Set entityTypes; Resource() { - this.entityType = null; + this.entityTypes = Collections.emptySet(); } - Resource(EntityType entityType) { - this.entityType = entityType; + Resource(EntityType... entityTypes) { + this.entityTypes = Set.of(entityTypes); } - public Optional getEntityType() { - return Optional.ofNullable(entityType); + public Set getEntityTypes() { + return entityTypes; } public static Resource of(EntityType entityType) { for (Resource resource : Resource.values()) { - if (resource.getEntityType().orElse(null) == entityType) { + if (resource.getEntityTypes().contains(entityType)) { return resource; } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 20e2001c87..4169aa1c2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker); put(Resource.TB_RESOURCE, systemEntityPermissionChecker); put(Resource.QUEUE, systemEntityPermissionChecker); + put(Resource.NOTIFICATION, systemEntityPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index ef7991e5de..cb924cabca 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.RPC, tenantEntityPermissionChecker); put(Resource.QUEUE, queuePermissionChecker); put(Resource.VERSION_CONTROL, PermissionChecker.allowAllPermissionChecker); + put(Resource.NOTIFICATION, tenantEntityPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 97b433661a..726a5d98f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -228,8 +228,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { if (userCredentials != null && isPositiveInteger(passwordPolicy.getPasswordReuseFrequencyDays())) { long passwordReuseFrequencyTs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(passwordPolicy.getPasswordReuseFrequencyDays()); - User user = userService.findUserById(tenantId, userCredentials.getUserId()); - JsonNode additionalInfo = user.getAdditionalInfo(); + JsonNode additionalInfo = userCredentials.getAdditionalInfo(); if (additionalInfo instanceof ObjectNode && additionalInfo.has(UserServiceImpl.USER_PASSWORD_HISTORY)) { JsonNode userPasswordHistoryJson = additionalInfo.get(UserServiceImpl.USER_PASSWORD_HISTORY); Map userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {}); diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index 39d5935906..aa57d1ee45 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java index 51f21be98f..85c33f31f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java +++ b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java b/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java index b01f94306a..c5ccd9ac78 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java +++ b/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java index 92eba5c4e2..bc984bbc35 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java +++ b/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java b/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java index 27931a9a64..60e3f45d2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java new file mode 100644 index 0000000000..a36b12f0fc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java @@ -0,0 +1,164 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.slack; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiRequest; +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import com.slack.api.methods.request.conversations.ConversationsListRequest; +import com.slack.api.methods.request.users.UsersListRequest; +import com.slack.api.methods.response.conversations.ConversationsListResponse; +import com.slack.api.methods.response.users.UsersListResponse; +import com.slack.api.model.ConversationType; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType; +import org.thingsboard.server.common.data.util.ThrowingBiFunction; +import org.thingsboard.server.dao.notification.NotificationSettingsService; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +@Service +@RequiredArgsConstructor +public class DefaultSlackService implements SlackService { + + private final NotificationSettingsService notificationSettingsService; + + private final Slack slack = Slack.getInstance(); + private final Cache> cache = Caffeine.newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .maximumSize(100) + .build(); + private static final int CONVERSATIONS_LOAD_LIMIT = 1000; + + @Override + public void sendMessage(TenantId tenantId, String token, String conversationId, String message) { + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(conversationId) + .text(message) + .build(); + sendRequest(token, request, MethodsClient::chatPostMessage); + } + + @Override + public List listConversations(TenantId tenantId, String token, SlackConversationType conversationType) { + return cache.get(conversationType + ":" + token, k -> { + if (conversationType == SlackConversationType.DIRECT) { + UsersListRequest request = UsersListRequest.builder() + .limit(CONVERSATIONS_LOAD_LIMIT) + .build(); + + UsersListResponse response = sendRequest(token, request, MethodsClient::usersList); + return response.getMembers().stream() + .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) + .map(user -> { + SlackConversation conversation = new SlackConversation(); + conversation.setId(user.getId()); + conversation.setShortName(user.getName()); + conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName()); + conversation.setEmail(user.getProfile() != null ? user.getProfile().getEmail() : null); + String title = "@" + conversation.getShortName(); + if (isNotEmpty(conversation.getWholeName()) && !conversation.getWholeName().equals(conversation.getShortName())) { + title += " (" + conversation.getWholeName() + ")"; + } + conversation.setTitle(title); + return conversation; + }) + .collect(Collectors.toList()); + } else { + ConversationsListRequest request = ConversationsListRequest.builder() + .types(List.of(conversationType == SlackConversationType.PUBLIC_CHANNEL ? + ConversationType.PUBLIC_CHANNEL : + ConversationType.PRIVATE_CHANNEL)) + .limit(CONVERSATIONS_LOAD_LIMIT) + .excludeArchived(true) + .build(); + + ConversationsListResponse response = sendRequest(token, request, MethodsClient::conversationsList); + return response.getChannels().stream() + .filter(channel -> !channel.isArchived()) + .map(channel -> { + SlackConversation conversation = new SlackConversation(); + conversation.setId(channel.getId()); + conversation.setShortName(channel.getName()); + conversation.setWholeName(channel.getNameNormalized()); + conversation.setTitle("#" + channel.getName()); + return conversation; + }) + .collect(Collectors.toList()); + } + }); + } + + @Override + public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) { + List conversations = listConversations(tenantId, token, conversationType); + return conversations.stream() + .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern)) + .findFirst().orElse(null); + } + + @Override + public String getToken(TenantId tenantId) { + NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); + SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) + settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); + if (slackConfig != null) { + return slackConfig.getBotToken(); + } else { + return null; + } + } + + private R sendRequest(String token, T request, ThrowingBiFunction method) { + MethodsClient client = slack.methods(token); + R response; + try { + response = method.apply(client, request); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + + if (!response.isOk()) { + String error = response.getError(); + if (error == null) { + error = "unknown error"; + } else if (error.contains("missing_scope")) { + String neededScope = response.getNeeded(); + error = "bot token scope '" + neededScope + "' is needed"; + } + throw new RuntimeException("Slack API error: " + error); + } + + return response; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java index 7997235ee1..73949552ec 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java index b42dcb6b83..6768259a84 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index b51221b1ba..7cd4998b5d 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java index 3e377ab6bb..2c022d2311 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java index 456ddb7a07..07244bf85e 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java index 8509b4e72f..b5fd663e4b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java index f9b010670d..dfd2d8477b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index d52c712074..60c159360b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -138,6 +139,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService PERSISTENT_ENTITY_FIELDS = Arrays.asList( new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "type"), + new EntityKey(EntityKeyType.ENTITY_FIELD, "label"), new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime")); private final TenantService tenantService; @@ -149,7 +151,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService { - TenantId tenantId = TenantId.fromUUID(id); try { + TenantId tenantId = TenantId.fromUUID(id); AssetId serviceAssetId = getServiceAssetId(tenantId, queueName); if (stats.getTotalMsgCounter().get() > 0) { List tsList = stats.getCounters().entrySet().stream() @@ -89,19 +89,19 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS tsService.saveAndNotifyInternal(tenantId, serviceAssetId, tsList, CALLBACK); } } - } catch (DataValidationException e) { - if (!e.getMessage().equalsIgnoreCase("Asset is referencing to non-existent tenant!")) { - throw e; + } catch (Exception e) { + if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e.getMessage())) { + log.debug("[{}] Failed to store the statistics", id, e); } } }); ruleEngineStats.getTenantExceptions().forEach((tenantId, e) -> { - TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry("ruleEngineException", e.toJsonString())); try { + TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry("ruleEngineException", e.toJsonString())); tsService.saveAndNotifyInternal(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), CALLBACK); - } catch (DataValidationException e2) { - if (!e2.getMessage().equalsIgnoreCase("Asset is referencing to non-existent tenant!")) { - throw e2; + } catch (Exception e2) { + if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) { + log.debug("[{}] Failed to store the statistics", tenantId, e2); } } }); diff --git a/application/src/main/java/org/thingsboard/server/service/stats/RuleEngineStatisticsService.java b/application/src/main/java/org/thingsboard/server/service/stats/RuleEngineStatisticsService.java index 6e82c00e1a..b7f0ceeec5 100644 --- a/application/src/main/java/org/thingsboard/server/service/stats/RuleEngineStatisticsService.java +++ b/application/src/main/java/org/thingsboard/server/service/stats/RuleEngineStatisticsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index cdec2ebfad..60fce13560 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.subscription; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; @@ -25,10 +25,11 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; @@ -59,8 +60,11 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DeviceStateService; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -81,34 +85,18 @@ import java.util.function.Predicate; @Slf4j @TbCoreComponent @Service +@RequiredArgsConstructor public class DefaultSubscriptionManagerService extends TbApplicationEventListener implements SubscriptionManagerService { - @Autowired - private AttributesService attrService; - - @Autowired - private TimeseriesService tsService; - - @Autowired - private NotificationsTopicService notificationsTopicService; - - @Autowired - private PartitionService partitionService; - - @Autowired - private TbServiceInfoProvider serviceInfoProvider; - - @Autowired - private TbQueueProducerProvider producerProvider; - - @Autowired - private TbLocalSubscriptionService localSubscriptionService; - - @Autowired - private DeviceStateService deviceStateService; - - @Autowired - private TbClusterService clusterService; + private final AttributesService attrService; + private final TimeseriesService tsService; + private final NotificationsTopicService notificationsTopicService; + private final PartitionService partitionService; + private final TbServiceInfoProvider serviceInfoProvider; + private final TbQueueProducerProvider producerProvider; + private final TbLocalSubscriptionService localSubscriptionService; + private final DeviceStateService deviceStateService; + private final TbClusterService clusterService; private final Map> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); @@ -268,7 +256,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene updateDeviceInactivityTimeout(tenantId, entityId, attributes); } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, - new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) + new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) , null); } } @@ -292,7 +280,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene } @Override - public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) { + public void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback) { onLocalAlarmSubUpdate(entityId, s -> { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -301,15 +289,14 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return null; } }, - s -> alarm.getCreatedTime() >= s.getTs(), - s -> alarm, - false + s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(), + alarm, false ); callback.onSuccess(); } @Override - public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) { + public void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback) { onLocalAlarmSubUpdate(entityId, s -> { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -319,12 +306,63 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene } }, s -> alarm.getCreatedTime() >= s.getTs(), - s -> alarm, - true + alarm, true ); callback.onSuccess(); } + @Override + public void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback) { + Set subscriptions = subscriptionsByEntityId.get(recipientId); + if (subscriptions != null) { + NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate); + log.trace("Handling notificationUpdate for user {}: {}", recipientId, notificationUpdate); + subscriptions.stream() + .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS + || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) + .forEach(subscription -> onNotificationsSubUpdate(subscriptionUpdate, subscription)); + } + callback.onSuccess(); + } + + @Override + public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) { + NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate); + subscriptionsByEntityId.forEach((entityId, subscriptions) -> { + if (entityId.getEntityType() != EntityType.USER) { + return; + } + log.trace("Handling notificationRequestUpdate for user {}: {}", entityId, notificationRequestUpdate); + subscriptions.forEach(subscription -> { + if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS && + subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) { + return; + } + if (!subscription.getTenantId().equals(tenantId)) { + return; + } + onNotificationsSubUpdate(subscriptionUpdate, subscription); + }); + }); + callback.onSuccess(); + } + + private void onNotificationsSubUpdate(NotificationsSubscriptionUpdate subscriptionUpdate, TbSubscription subscription) { + if (serviceId.equals(subscription.getServiceId())) { + log.trace("[{}][{}][{}] Subscription session is managed by current service, forwarding to localSubscriptionService (update: {})", + subscription.getServiceId(), subscription.getEntityId(), subscription.getSessionId(), subscriptionUpdate); + localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), + subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY); + } else { + log.trace("[{}][{}][{}] Subscription session is not managed by current service (update: {})", + subscription.getServiceId(), subscription.getEntityId(), subscription.getSessionId(), subscriptionUpdate); + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId()); + ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate); + TbProtoQueueMsg queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto); + toCoreNotificationsProducer.send(tpi, queueMsg, null); + } + } + @Override public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, TbCallback callback) { onLocalTelemetrySubUpdate(entityId, @@ -354,7 +392,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene deleteDeviceInactivityTimeout(tenantId, entityId, keys); } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId, - new DeviceId(entityId.getId()), scope, keys), null); + new DeviceId(entityId.getId()), scope, keys), null); } } callback.onSuccess(); @@ -414,19 +452,20 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene private void onLocalAlarmSubUpdate(EntityId entityId, Function castFunction, Predicate filterFunction, - Function processFunction, boolean deleted) { + AlarmInfo alarm, boolean deleted) { Set entitySubscriptions = subscriptionsByEntityId.get(entityId); + if (alarm == null) { + log.warn("[{}] empty alarm update!", entityId); + return; + } if (entitySubscriptions != null) { entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { - Alarm alarm = processFunction.apply(s); - if (alarm != null) { - if (serviceId.equals(s.getServiceId())) { - AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted); - localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); - } else { - TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); - toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null); - } + if (serviceId.equals(s.getServiceId())) { + AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted); + localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); + } else { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); + toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null); } }); } else { @@ -557,12 +596,12 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( - LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build()) + LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build()) .build(); return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } - private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, boolean deleted) { + private TbProtoQueueMsg toProto(TbSubscription subscription, AlarmInfo alarm, boolean deleted) { TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder(); builder.setSessionId(subscription.getSessionId()); @@ -571,8 +610,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene builder.setDeleted(deleted); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( - LocalSubscriptionServiceMsgProto.newBuilder() - .setAlarmSubUpdate(builder.build()).build()) + LocalSubscriptionServiceMsgProto.newBuilder() + .setAlarmSubUpdate(builder.build()).build()) .build(); return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 3d9077cb92..4b37ba26b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,22 +49,22 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.AggHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AggKey; -import org.thingsboard.server.service.telemetry.cmd.v2.AggTimeSeriesCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggTimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.GetTsCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -95,8 +95,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private static final int DEFAULT_LIMIT = 100; private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); - @Autowired - private TelemetryWebSocketService wsService; + @Autowired @Lazy + private WebSocketService wsService; @Autowired private EntityService entityService; @@ -167,7 +167,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } @Override - public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) { + public void handleCmd(WebSocketSessionRef session, EntityDataCmd cmd) { TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); if (ctx != null) { log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd); @@ -358,7 +358,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } @Override - public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) { + public void handleCmd(WebSocketSessionRef session, EntityCountCmd cmd) { TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); if (ctx == null) { ctx = createSubCtx(session, cmd); @@ -378,7 +378,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } @Override - public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) { + public void handleCmd(WebSocketSessionRef session, AlarmDataCmd cmd) { TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); if (ctx == null) { log.debug("[{}][{}] Creating new alarm subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd); @@ -409,6 +409,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } } + @Override + public void handleCmd(WebSocketSessionRef session, AlarmCountCmd cmd) { + TbAlarmCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); + if (ctx == null) { + ctx = createSubCtx(session, cmd); + long start = System.currentTimeMillis(); + ctx.fetchData(); + long end = System.currentTimeMillis(); + stats.getAlarmQueryInvocationCnt().incrementAndGet(); + stats.getAlarmQueryTimeSpent().addAndGet(end - start); + TbAlarmCountSubCtx finalCtx = ctx; + ScheduledFuture task = scheduler.scheduleWithFixedDelay( + () -> refreshDynamicQuery(finalCtx), + dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); + finalCtx.setRefreshTask(task); + } else { + log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd); + } + } + private boolean validate(TbAbstractSubCtx finalCtx) { if (finalCtx.isStopped()) { log.warn("[{}][{}][{}] Received validation task for already stopped context.", finalCtx.getTenantId(), finalCtx.getSessionId(), finalCtx.getCmdId()); @@ -469,7 +489,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } } - private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { + private TbEntityDataSubCtx createSubCtx(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); @@ -480,7 +500,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc return ctx; } - private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { + private TbEntityCountSubCtx createSubCtx(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId()); @@ -492,7 +512,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } - private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { + private TbAlarmDataSubCtx createSubCtx(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription, @@ -502,6 +522,17 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc return ctx; } + private TbAlarmCountSubCtx createSubCtx(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { + Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); + TbAlarmCountSubCtx ctx = new TbAlarmCountSubCtx(serviceId, wsService, entityService, localSubscriptionService, + attributesService, stats, alarmService, sessionRef, cmd.getCmdId()); + if (cmd.getQuery() != null) { + ctx.setAndResolveQuery(cmd.getQuery()); + } + sessionSubs.put(cmd.getCmdId(), ctx); + return ctx; + } + @SuppressWarnings("unchecked") private T getSubCtx(String sessionId, int cmdId) { Map sessionSubs = subscriptionsBySessionId.get(sessionId); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 968e15ea4b..64f1de8662 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,18 +21,19 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; -import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; -import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -152,7 +153,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value)); break; } - subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); } callback.onSuccess(); } @@ -163,7 +164,17 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) { - subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); + } + callback.onSuccess(); + } + + @Override + public void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback) { + TbSubscription subscription = subscriptionsBySessionId.getOrDefault(sessionId, Collections.emptyMap()).get(subscriptionId); + if (subscription != null && (subscription.getType() == TbSubscriptionType.NOTIFICATIONS + || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)) { + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update)); } callback.onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java index 28ca2f6729..4cfd218893 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.thingsboard.server.service.subscription; import lombok.Data; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.service.telemetry.cmd.v2.AggKey; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; @Data public class ReadTsKvQueryInfo { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java rename to application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java index e5fe16de01..999a447d7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.subscription; public enum SubscriptionErrorCode { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 15aab86327..1db4348828 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,17 @@ package org.thingsboard.server.service.subscription; import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import java.util.List; @@ -42,9 +47,12 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback); - void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); + void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback); - void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); + void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback); + void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback); + + void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionServiceStatistics.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionServiceStatistics.java index 5c1bb937dd..6ede001d8e 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionServiceStatistics.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionServiceStatistics.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index 271de08733..084e73ec37 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,9 +29,9 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.Arrays; @@ -50,10 +50,10 @@ public abstract class TbAbstractDataSubCtx data; - public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, + public TbAbstractDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, - TelemetryWebSocketSessionRef sessionRef, int cmdId) { + WebSocketSessionRef sessionRef, int cmdId) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.subToEntityIdMap = new ConcurrentHashMap<>(); } @@ -185,7 +185,7 @@ public abstract class TbAbstractDataSubCtx sendWsMsg(s, subscriptionUpdate, keysType)) + .updateProcessor((sub, subscriptionUpdate) -> sendWsMsg(sub.getSessionId(), subscriptionUpdate, keysType)) .allKeys(false) .keyStates(keyStates) .scope(scope) @@ -219,7 +219,7 @@ public abstract class TbAbstractDataSubCtx sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) + .updateProcessor((sub, subscriptionUpdate) -> sendWsMsg(sub.getSessionId(), subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) .allKeys(false) .keyStates(keyStates) .latestValues(latestValues) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java index 20e81f0662..d6b3052613 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.EntityCountQuery; -import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.FilterPredicateType; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilterPredicate; @@ -39,10 +38,10 @@ import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.HashMap; @@ -64,11 +63,11 @@ public abstract class TbAbstractSubCtx { protected final Lock wsLock = new ReentrantLock(true); protected final String serviceId; protected final SubscriptionServiceStatistics stats; - private final TelemetryWebSocketService wsService; + private final WebSocketService wsService; protected final EntityService entityService; protected final TbLocalSubscriptionService localSubscriptionService; protected final AttributesService attributesService; - protected final TelemetryWebSocketSessionRef sessionRef; + protected final WebSocketSessionRef sessionRef; protected final int cmdId; protected final Set subToDynamicValueKeySet; @Getter @@ -80,10 +79,10 @@ public abstract class TbAbstractSubCtx { protected volatile ScheduledFuture refreshTask; protected volatile boolean stopped; - public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService, + public TbAbstractSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, - TelemetryWebSocketSessionRef sessionRef, int cmdId) { + WebSocketSessionRef sessionRef, int cmdId) { this.serviceId = serviceId; this.wsService = wsService; this.entityService = entityService; @@ -142,7 +141,7 @@ public abstract class TbAbstractSubCtx { .subscriptionId(subIdx) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) - .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) + .updateProcessor((subscription, subscriptionUpdate) -> dynamicValueSubUpdate(subscription.getSessionId(), subscriptionUpdate, dynamicValueKeySubMap)) .allKeys(false) .keyStates(keyStates) .scope(TbAttributeSubscriptionScope.SERVER_SCOPE) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java new file mode 100644 index 0000000000..bb7b9350fb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java @@ -0,0 +1,67 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.subscription; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.query.AlarmCountQuery; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; + +@Slf4j +@ToString(callSuper = true) +public class TbAlarmCountSubCtx extends TbAbstractSubCtx { + + private final AlarmService alarmService; + + @Getter + @Setter + private volatile int result; + + public TbAlarmCountSubCtx(String serviceId, WebSocketService wsService, + EntityService entityService, TbLocalSubscriptionService localSubscriptionService, + AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, + WebSocketSessionRef sessionRef, int cmdId) { + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); + this.alarmService = alarmService; + } + + @Override + public void fetchData() { + result = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query); + sendWsMsg(new AlarmCountUpdate(cmdId, result)); + } + + @Override + protected void update() { + int newCount = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query); + if (newCount != result) { + result = newCount; + sendWsMsg(new AlarmCountUpdate(cmdId, result)); + } + } + + @Override + public boolean isDynamic() { + return true; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index 30b58d92a8..ccba16fbd8 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,9 @@ import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; +import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.Aggregation; @@ -39,11 +41,11 @@ import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.Collection; @@ -63,10 +65,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { private final AlarmService alarmService; @Getter - @Setter private final LinkedHashMap entitiesMap; @Getter - @Setter private final HashMap alarmsMap; private final int maxEntitiesPerAlarmSubscription; @@ -82,10 +82,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { private int alarmInvocationAttempts; - public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService, + public TbAlarmDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, - TelemetryWebSocketSessionRef sessionRef, int cmdId, + WebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerAlarmSubscription, int maxAlarmQueriesPerRefreshInterval) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription; @@ -176,7 +176,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { .subscriptionId(subIdx) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityData.getEntityId()) - .updateConsumer(this::sendWsMsg) + .updateProcessor((sub, update) -> sendWsMsg(sub.getSessionId(), update)) .ts(startTs) .build(); localSubscriptionService.addSubscription(subscription); @@ -225,8 +225,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { boolean matchesFilter = filter(alarm); if (onCurrentPage) { if (matchesFilter) { - AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId()); - updated.getLatest().putAll(current.getLatest()); + AlarmData updated = new AlarmData(subscriptionUpdate.getAlarm(), current); alarmsMap.put(alarmId, updated); sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); } else { @@ -270,8 +269,24 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { if (filter.getStatusList() != null && !filter.getStatusList().isEmpty()) { boolean matches = false; for (AlarmSearchStatus status : filter.getStatusList()) { - if (status.getStatuses().contains(alarm.getStatus())) { - matches = true; + switch (status) { + case ANY: + matches = true; + break; + case ACK: + matches = alarm.isAcknowledged(); + break; + case UNACK: + matches = !alarm.isAcknowledged(); + break; + case CLEARED: + matches = alarm.isCleared(); + break; + case ACTIVE: + matches = !alarm.isCleared(); + break; + } + if (matches) { break; } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java index fba351f755..bc23d1a9b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,10 @@ package org.thingsboard.server.service.subscription; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; -import java.util.List; import java.util.function.BiConsumer; public class TbAlarmsSubscription extends TbSubscription { @@ -33,8 +30,8 @@ public class TbAlarmsSubscription extends TbSubscription updateConsumer, long ts) { - super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateConsumer); + BiConsumer, AlarmSubscriptionUpdate> updateProcessor, long ts) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateProcessor); this.ts = ts; } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java index ebd3fe548d..3746758caa 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.Map; import java.util.function.BiConsumer; @@ -32,9 +32,9 @@ public class TbAttributeSubscription extends TbSubscription updateConsumer, + BiConsumer, TelemetrySubscriptionUpdate> updateProcessor, boolean allKeys, Map keyStates, TbAttributeSubscriptionScope scope) { - super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES, updateConsumer); + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES, updateProcessor); this.allKeys = allKeys; this.keyStates = keyStates; this.scope = scope; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java index ae23d26218..b3874e28fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java index 7fc87eb17c..eac94cde7d 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,18 +19,18 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; @Slf4j public class TbEntityCountSubCtx extends TbAbstractSubCtx { private volatile int result; - public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, + public TbEntityCountSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, - SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) { + SubscriptionServiceStatistics stats, WebSocketSessionRef sessionRef, int cmdId) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index 016ed198ec..924d30d5c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,13 +28,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.Collections; @@ -59,9 +59,9 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { private final int maxEntitiesPerDataSubscription; private Map> latestTsEntityData; - public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, + public TbEntityDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, - SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) { + SubscriptionServiceStatistics stats, WebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription; } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java index 784fa7d842..097fa792f9 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,22 @@ */ package org.thingsboard.server.service.subscription; -import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; public interface TbEntityDataSubscriptionService { - void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd); + void handleCmd(WebSocketSessionRef sessionId, EntityDataCmd cmd); - void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd); + void handleCmd(WebSocketSessionRef sessionId, EntityCountCmd cmd); - void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd); + void handleCmd(WebSocketSessionRef sessionId, AlarmDataCmd cmd); + + void handleCmd(WebSocketSessionRef sessionId, AlarmCountCmd cmd); void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java index e398111f1f..516eb20684 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,9 @@ package org.thingsboard.server.service.subscription; import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; public interface TbLocalSubscriptionService { @@ -33,6 +34,8 @@ public interface TbLocalSubscriptionService { void onSubscriptionUpdate(String sessionId, AlarmSubscriptionUpdate update, TbCallback callback); + void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback); + void onApplicationEvent(PartitionChangeEvent event); void onApplicationEvent(ClusterTopologyChangeEvent event); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java index 95078d8504..dfad6a41de 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ public abstract class TbSubscription { private final TenantId tenantId; private final EntityId entityId; private final TbSubscriptionType type; - private final BiConsumer updateConsumer; + private final BiConsumer, T> updateProcessor; @Override public boolean equals(Object o) { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java index 86a98ba682..cd560bbbaf 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,5 +16,5 @@ package org.thingsboard.server.service.subscription; public enum TbSubscriptionType { - TIMESERIES, ATTRIBUTES, ALARMS + TIMESERIES, ATTRIBUTES, ALARMS, NOTIFICATIONS, NOTIFICATIONS_COUNT } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index f8359a21ee..914c8a7c55 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,12 @@ package org.thingsboard.server.service.subscription; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -49,10 +52,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeletePr import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; import java.util.HashMap; @@ -105,6 +113,17 @@ public class TbSubscriptionUtils { .setTs(alarmSub.getTs()); msgBuilder.setAlarmSub(alarmSubProto.build()); break; + case NOTIFICATIONS: + NotificationsSubscription notificationsSub = (NotificationsSubscription) subscription; + msgBuilder.setNotificationsSub(TransportProtos.NotificationsSubscriptionProto.newBuilder() + .setSub(subscriptionProto) + .setLimit(notificationsSub.getLimit())); + break; + case NOTIFICATIONS_COUNT: + NotificationsCountSubscription notificationsCountSub = (NotificationsCountSubscription) subscription; + msgBuilder.setNotificationsCountSub(TransportProtos.NotificationsCountSubscriptionProto.newBuilder() + .setSub(subscriptionProto)); + break; } return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } @@ -166,6 +185,29 @@ public class TbSubscriptionUtils { return builder.build(); } + public static NotificationsSubscription fromProto(TransportProtos.NotificationsSubscriptionProto notificationsSub) { + TbSubscriptionProto sub = notificationsSub.getSub(); + return NotificationsSubscription.builder() + .serviceId(sub.getServiceId()) + .sessionId(sub.getSessionId()) + .subscriptionId(sub.getSubscriptionId()) + .tenantId(TenantId.fromUUID(new UUID(sub.getTenantIdMSB(), sub.getTenantIdLSB()))) + .entityId(EntityIdFactory.getByTypeAndUuid(sub.getEntityType(), new UUID(sub.getEntityIdMSB(), sub.getEntityIdLSB()))) + .limit(notificationsSub.getLimit()) + .build(); + } + + public static NotificationsCountSubscription fromProto(TransportProtos.NotificationsCountSubscriptionProto notificationsCountSub) { + TbSubscriptionProto sub = notificationsCountSub.getSub(); + return NotificationsCountSubscription.builder() + .serviceId(sub.getServiceId()) + .sessionId(sub.getSessionId()) + .subscriptionId(sub.getSubscriptionId()) + .tenantId(TenantId.fromUUID(new UUID(sub.getTenantIdMSB(), sub.getTenantIdLSB()))) + .entityId(EntityIdFactory.getByTypeAndUuid(sub.getEntityType(), new UUID(sub.getEntityIdMSB(), sub.getEntityIdLSB()))) + .build(); + } + public static TelemetrySubscriptionUpdate fromProto(TbSubscriptionUpdateProto proto) { if (proto.getErrorCode() > 0) { return new TelemetrySubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); @@ -189,8 +231,8 @@ public class TbSubscriptionUtils { if (proto.getErrorCode() > 0) { return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg()); } else { - Alarm alarm = JacksonUtil.fromString(proto.getAlarm(), Alarm.class); - return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm); + AlarmInfo alarm = JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class); + return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, proto.getDeleted()); } } @@ -316,7 +358,7 @@ public class TbSubscriptionUtils { return entry; } - public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, Alarm alarm) { + public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) { TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); @@ -329,7 +371,7 @@ public class TbSubscriptionUtils { return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } - public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, Alarm alarm) { + public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) { TbAlarmDeleteProto.Builder builder = TbAlarmDeleteProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); @@ -341,4 +383,50 @@ public class TbSubscriptionUtils { msgBuilder.setAlarmDelete(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } + + public static ToCoreNotificationMsg notificationsSubUpdateToProto(TbSubscription subscription, NotificationsSubscriptionUpdate update) { + TransportProtos.NotificationsSubscriptionUpdateProto.Builder updateProto = TransportProtos.NotificationsSubscriptionUpdateProto.newBuilder() + .setSessionId(subscription.getSessionId()) + .setSubscriptionId(subscription.getSubscriptionId()); + if (update.getNotificationUpdate() != null) { + updateProto.setNotificationUpdate(JacksonUtil.toString(update.getNotificationUpdate())); + } + if (update.getNotificationRequestUpdate() != null) { + updateProto.setNotificationRequestUpdate(JacksonUtil.toString(update.getNotificationRequestUpdate())); + } + return ToCoreNotificationMsg.newBuilder() + .setToLocalSubscriptionServiceMsg(TransportProtos.LocalSubscriptionServiceMsgProto.newBuilder() + .setNotificationsSubUpdate(updateProto) + .build()) + .build(); + } + + public static ToCoreMsg notificationUpdateToProto(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate) { + TransportProtos.NotificationUpdateProto updateProto = TransportProtos.NotificationUpdateProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setRecipientIdMSB(recipientId.getId().getMostSignificantBits()) + .setRecipientIdLSB(recipientId.getId().getLeastSignificantBits()) + .setUpdate(JacksonUtil.toString(notificationUpdate)) + .build(); + return ToCoreMsg.newBuilder() + .setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder() + .setNotificationUpdate(updateProto) + .build()) + .build(); + } + + public static ToCoreNotificationMsg notificationRequestUpdateToProto(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate) { + TransportProtos.NotificationRequestUpdateProto updateProto = TransportProtos.NotificationRequestUpdateProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setUpdate(JacksonUtil.toString(notificationRequestUpdate)) + .build(); + return ToCoreNotificationMsg.newBuilder() + .setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder() + .setNotificationRequestUpdate(updateProto) + .build()) + .build(); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java index e08f25a280..400cf07e7e 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.Map; import java.util.function.BiConsumer; @@ -39,9 +39,9 @@ public class TbTimeseriesSubscription extends TbSubscription updateConsumer, + BiConsumer, TelemetrySubscriptionUpdate> updateProcessor, boolean allKeys, Map keyStates, long startTime, long endTime, boolean latestValues) { - super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateConsumer); + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateProcessor); this.allKeys = allKeys; this.keyStates = keyStates; this.startTime = startTime; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index f4e4e3dc14..b646cb7ef6 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,14 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.apiusage.RateLimitService; +import org.thingsboard.server.service.apiusage.limits.LimitedApi; +import org.thingsboard.server.service.apiusage.limits.RateLimitService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService; @@ -72,7 +73,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS @Override public , I extends EntityId> EntityExportData exportEntity(EntitiesExportCtx ctx, I entityId) throws ThingsboardException { - if (!rateLimitService.checkEntityExportLimit(ctx.getTenantId())) { + if (!rateLimitService.checkRateLimit(ctx.getTenantId(), LimitedApi.ENTITY_EXPORT)) { throw new ThingsboardException("Rate limit for entities export is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); } @@ -84,7 +85,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS @Override public , I extends EntityId> EntityImportResult importEntity(EntitiesImportCtx ctx, EntityExportData exportData) throws ThingsboardException { - if (!rateLimitService.checkEntityImportLimit(ctx.getTenantId())) { + if (!rateLimitService.checkRateLimit(ctx.getTenantId(), LimitedApi.ENTITY_IMPORT)) { throw new ThingsboardException("Rate limit for entities import is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); } if (exportData.getEntity() == null || exportData.getEntity().getId() == null) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.java index 8657528dac..0747e4ce14 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java index b5f5e6afd9..456eeb8b86 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java index 505d1b2a3f..09db98853f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java index 0a99979e8c..c5c26c4476 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java index 5ae496558e..fbbaa51bd2 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java index a333767b33..340298db7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java index 01626368dc..891a6411a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java index 9a3b195b57..618729a9f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java index a00a7c09aa..a9e0cf1166 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java index bc5136bee6..ddb6d98135 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java index 46423c66ef..82ce17893a 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.java index 3fff890d3a..715c910efe 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java index 802a03ab94..775224cc63 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java index d3dd23910f..7024f36623 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java index 9437f21e60..a3b8cd98a9 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index d78403a884..b34ba3ef66 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java index d48e9a3d23..bc9446b39d 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java index fa3558520e..9ba81a3f8d 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java index c6b33a5bd2..7f0af64ed7 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java index 4fdd6cf6ab..f72ac8a11f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java index d12ed6ecff..d6795e0834 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java index 5aa97a6f4d..2da996953f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java index 2cf2c57c11..1579909463 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java index 72be8e6bf7..efa305f907 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java index 6856323ecc..9ce682a3fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java index 1e869429d8..994cea1bdc 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java index a0a961bfef..1de6f06c49 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java index f01eb15337..eb54fce21e 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java index adbbcf6a09..8dc748c43f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index 8cf17fd5b0..8ffd4796fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java index 413980d119..785dd50a92 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.CollectionsUtil; +import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; @@ -64,7 +64,6 @@ import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest; import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; -import org.thingsboard.server.service.sync.vc.data.ContentsDiffGitRequest; import org.thingsboard.server.service.sync.vc.data.EntitiesContentGitRequest; import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest; import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest; @@ -78,11 +77,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java index 8a2410e932..d43a2c45e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java index feba7db76d..78311ac23f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java index 13d8280046..055eb707cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java index 3a674eece0..7d0aaa5fc9 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java index dcf3ef3a70..4524f8efb8 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java index e58ceba8c5..0c874f63a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java index 6215744f7a..5fccbb3cbb 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java index 47b9e5f5d2..9d4e63201f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java index e3c918818c..b3c47cf2a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java index b6e8d45ec2..7c13b8f46e 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java index 51978481e1..edc9b84451 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.java index 09245eb895..3942151fc8 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java index a989439d10..0d1b80d69c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.java index e8577b049d..4b7cc3430b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java index 7c3fb7a600..9a744abcca 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.java index ad653edd0c..e1d68c2c2b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.java index 4c89c91271..9046d66b30 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java index fddc91883e..8bbb8e2498 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java index 6a1d60a065..3353240366 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java index 80d90590a1..ecaddcdea4 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java index 4d89efa14d..73f13764f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java index 3fc531735e..d29fb295fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java index d40a589646..c81dda1906 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java index b34806a6ca..6bcb74b62b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java index 97432adbb8..c4f4ae5bcd 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.java index 4cf5d93765..c163fb7fa3 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java index e73706609f..7e48f2a3e5 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java index c48bfa7a03..e411f3cf39 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java index 555490bd06..656e484e67 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java index 60b7f50e12..17c8819669 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java index a49ace3ec2..27fba3459c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java index 946d06c87c..6498669405 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java new file mode 100644 index 0000000000..caf3abefa0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -0,0 +1,214 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.system; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ProtocolStringList; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.FeaturesInfo; +import org.thingsboard.server.common.data.SystemInfo; +import org.thingsboard.server.common.data.SystemInfoData; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; +import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; +import org.thingsboard.server.queue.discovery.DiscoveryService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; + +import javax.annotation.Nullable; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.thingsboard.common.util.SystemUtil.getCpuUsage; +import static org.thingsboard.common.util.SystemUtil.getMemoryUsage; +import static org.thingsboard.common.util.SystemUtil.getDiscSpaceUsage; +import static org.thingsboard.common.util.SystemUtil.getCpuCount; +import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace; +import static org.thingsboard.common.util.SystemUtil.getTotalMemory; + +@TbCoreComponent +@Service +@RequiredArgsConstructor +@Slf4j +public class DefaultSystemInfoService extends TbApplicationEventListener implements SystemInfoService { + + public static final FutureCallback CALLBACK = new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Integer result) { + } + + @Override + public void onFailure(Throwable t) { + log.warn("Failed to persist system info", t); + } + }; + + private final TbServiceInfoProvider serviceInfoProvider; + private final PartitionService partitionService; + private final DiscoveryService discoveryService; + private final TelemetrySubscriptionService telemetryService; + private final TbApiUsageStateClient apiUsageStateClient; + private final AdminSettingsService adminSettingsService; + private final OAuth2Service oAuth2Service; + private volatile ScheduledExecutorService scheduler; + + @Override + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { + boolean myPartition = partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition(); + synchronized (this) { + if (myPartition) { + if (scheduler == null) { + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("tb-system-info-scheduler")); + scheduler.scheduleAtFixedRate(this::saveCurrentSystemInfo, 0, 1, TimeUnit.MINUTES); + } + } else { + destroy(); + } + } + } + } + + @Override + public SystemInfo getSystemInfo() { + SystemInfo systemInfo = new SystemInfo(); + + if (discoveryService.isMonolith()) { + systemInfo.setMonolith(true); + systemInfo.setSystemData(Collections.singletonList(createSystemInfoData(serviceInfoProvider.generateNewServiceInfoWithCurrentSystemInfo()))); + } else { + systemInfo.setSystemData(getSystemData(serviceInfoProvider.getServiceInfo())); + } + + return systemInfo; + } + + protected void saveCurrentSystemInfo() { + if (discoveryService.isMonolith()) { + saveCurrentMonolithSystemInfo(); + } else { + saveCurrentClusterSystemInfo(); + } + } + + @Override + public FeaturesInfo getFeaturesInfo() { + FeaturesInfo featuresInfo = new FeaturesInfo(); + featuresInfo.setEmailEnabled(isEmailEnabled()); + featuresInfo.setSmsEnabled(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "sms") != null); + featuresInfo.setOauthEnabled(oAuth2Service.findOAuth2Info().isEnabled()); + featuresInfo.setTwoFaEnabled(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "twoFaSettings") != null); + featuresInfo.setNotificationEnabled(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "notifications") != null); + return featuresInfo; + } + + private boolean isEmailEnabled() { + AdminSettings mailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); + if (mailSettings != null) { + JsonNode mailFrom = mailSettings.getJsonValue().get("mailFrom"); + if (mailFrom != null) { + return DataValidator.doValidateEmail(mailFrom.asText()); + } + } + return false; + } + + private void saveCurrentClusterSystemInfo() { + long ts = System.currentTimeMillis(); + List clusterSystemData = getSystemData(serviceInfoProvider.getServiceInfo()); + BasicTsKvEntry clusterDataKv = new BasicTsKvEntry(ts, new JsonDataEntry("clusterSystemData", JacksonUtil.toString(clusterSystemData))); + doSave(Collections.singletonList(clusterDataKv)); + } + + private void saveCurrentMonolithSystemInfo() { + long ts = System.currentTimeMillis(); + List tsList = new ArrayList<>(); + + getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v)))); + getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v)))); + getDiscSpaceUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v)))); + + getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v)))); + getTotalMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalMemory", v)))); + getTotalDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalDiscSpace", v)))); + + doSave(tsList); + } + + private void doSave(List telemetry) { + ApiUsageState apiUsageState = apiUsageStateClient.getApiUsageState(TenantId.SYS_TENANT_ID); + telemetryService.saveAndNotifyInternal(TenantId.SYS_TENANT_ID, apiUsageState.getId(), telemetry, CALLBACK); + } + + private List getSystemData(ServiceInfo serviceInfo) { + List clusterSystemData = new ArrayList<>(); + clusterSystemData.add(createSystemInfoData(serviceInfo)); + this.discoveryService.getOtherServers() + .stream() + .map(this::createSystemInfoData) + .forEach(clusterSystemData::add); + return clusterSystemData; + } + + private SystemInfoData createSystemInfoData(ServiceInfo serviceInfo) { + ProtocolStringList serviceTypes = serviceInfo.getServiceTypesList(); + SystemInfoData infoData = new SystemInfoData(); + infoData.setServiceId(serviceInfo.getServiceId()); + infoData.setServiceType(serviceTypes.size() > 1 ? "MONOLITH" : serviceTypes.get(0)); + + infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage()); + infoData.setMemoryUsage(serviceInfo.getSystemInfo().getMemoryUsage()); + infoData.setDiscUsage(serviceInfo.getSystemInfo().getDiskUsage()); + + infoData.setCpuCount(serviceInfo.getSystemInfo().getCpuCount()); + infoData.setTotalMemory(serviceInfo.getSystemInfo().getTotalMemory()); + infoData.setTotalDiscSpace(serviceInfo.getSystemInfo().getTotalDiscSpace()); + + return infoData; + } + + @PreDestroy + private void destroy() { + if (scheduler != null) { + scheduler.shutdownNow(); + scheduler = null; + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/system/SystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/SystemInfoService.java new file mode 100644 index 0000000000..1ca0fb8fe0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/system/SystemInfoService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.system; + +import org.thingsboard.server.common.data.FeaturesInfo; +import org.thingsboard.server.common.data.SystemInfo; + +public interface SystemInfoService { + SystemInfo getSystemInfo(); + + FeaturesInfo getFeaturesInfo(); +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 7645465b19..32efaa6346 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,17 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; -import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import javax.annotation.Nullable; @@ -38,33 +42,26 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; +import java.util.function.Supplier; /** * Created by ashvayka on 27.03.18. */ @Slf4j -public abstract class AbstractSubscriptionService extends TbApplicationEventListener{ +public abstract class AbstractSubscriptionService extends TbApplicationEventListener { protected final Set currentPartitions = ConcurrentHashMap.newKeySet(); - protected final TbClusterService clusterService; - protected final PartitionService partitionService; + @Autowired + protected TbClusterService clusterService; + @Autowired + protected PartitionService partitionService; + @Autowired protected Optional subscriptionManagerService; protected ExecutorService wsCallBackExecutor; - public AbstractSubscriptionService(TbClusterService clusterService, - PartitionService partitionService) { - this.clusterService = clusterService; - this.partitionService = partitionService; - } - - @Autowired(required = false) - public void setSubscriptionManagerService(Optional subscriptionManagerService) { - this.subscriptionManagerService = subscriptionManagerService; - } - - abstract String getExecutorPrefix(); + protected abstract String getExecutorPrefix(); @PostConstruct public void initExecutor() { @@ -86,6 +83,22 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList } } + protected void forwardToSubscriptionManagerService(TenantId tenantId, EntityId entityId, + Consumer toSubscriptionManagerService, + Supplier toCore) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); + if (currentPartitions.contains(tpi)) { + if (subscriptionManagerService.isPresent()) { + toSubscriptionManagerService.accept(subscriptionManagerService.get()); + } else { + log.warn("Possible misconfiguration because subscriptionManagerService is null!"); + } + } else { + TransportProtos.ToCoreMsg toCoreMsg = toCore.get(); + clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); + } + } + protected void addWsCallback(ListenableFuture saveFuture, Consumer callback) { Futures.addCallback(saveFuture, new FutureCallback() { @Override @@ -98,4 +111,5 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList } }, wsCallBackExecutor); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java index a80ec0f3f4..ff1228a27b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java index 5eb258c2a6..b7c7b281ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 956dde8024..07243711f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,70 +19,97 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; +import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmModificationRequest; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; -import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.service.subscription.SubscriptionManagerService; +import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import java.util.Collection; -import java.util.Optional; /** * Created by ashvayka on 27.03.18. */ @Service @Slf4j +@RequiredArgsConstructor public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { private final AlarmService alarmService; + private final TbAlarmCommentService alarmCommentService; private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; + private final NotificationRuleProcessor notificationRuleProcessor; - public DefaultAlarmSubscriptionService(TbClusterService clusterService, - PartitionService partitionService, - AlarmService alarmService, - TbApiUsageReportClient apiUsageClient, - TbApiUsageStateService apiUsageStateService) { - super(clusterService, partitionService); - this.alarmService = alarmService; - this.apiUsageClient = apiUsageClient; - this.apiUsageStateService = apiUsageStateService; + @Override + protected String getExecutorPrefix() { + return "alarm"; } - @Autowired(required = false) - public void setSubscriptionManagerService(Optional subscriptionManagerService) { - this.subscriptionManagerService = subscriptionManagerService; + @Override + public AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request) { + boolean creationEnabled = apiUsageStateService.getApiUsageState(request.getTenantId()).isAlarmCreationEnabled(); + var result = alarmService.createAlarm(request, creationEnabled); + if (result.isCreated()) { + apiUsageClient.report(request.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); + } + return withWsCallback(request, result); } @Override - String getExecutorPrefix() { - return "alarm"; + public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) { + return withWsCallback(alarmService.updateAlarm(request)); + } + + @Override + public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) { + return withWsCallback(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs)); + } + + @Override + public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) { + return withWsCallback(alarmService.clearAlarm(tenantId, alarmId, clearTs, details)); + } + + @Override + public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) { + return withWsCallback(alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs)); + } + + @Override + public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) { + return withWsCallback(alarmService.unassignAlarm(tenantId, alarmId, assignTs)); } @Override @@ -90,6 +117,19 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled()); if (result.isSuccessful()) { onAlarmUpdated(result); + AlarmSeverity oldSeverity = result.getOldSeverity(); + if (oldSeverity != null && !oldSeverity.equals(result.getAlarm().getSeverity())) { + AlarmComment alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm severity was updated from %s to %s", oldSeverity, result.getAlarm().getSeverity()))) + .build(); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, null); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + } } if (result.isCreated()) { apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); @@ -99,29 +139,29 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService @Override public Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId) { - AlarmOperationResult result = alarmService.deleteAlarm(tenantId, alarmId); + AlarmApiCallResult result = alarmService.delAlarm(tenantId, alarmId); onAlarmDeleted(result); return result.isSuccessful(); } @Override public ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) { - ListenableFuture result = alarmService.ackAlarm(tenantId, alarmId, ackTs); + ListenableFuture result = Futures.immediateFuture(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs)); Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); - return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + return Futures.transform(result, AlarmApiCallResult::isSuccessful, wsCallBackExecutor); } @Override public ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { - ListenableFuture result = clearAlarmForResult(tenantId, alarmId, details, clearTs); - return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details); + return Futures.transform(Futures.immediateFuture(result), AlarmApiCallResult::isSuccessful, wsCallBackExecutor); } @Override public ListenableFuture clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { - ListenableFuture result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs); - Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); - return result; + AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details); + Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor); + return Futures.immediateFuture(new AlarmOperationResult(result)); } @Override @@ -135,8 +175,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } @Override - public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { - return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId); + public AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId) { + return alarmService.findAlarmInfoById(tenantId, alarmId); } @Override @@ -150,8 +190,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } @Override - public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { - return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus); + public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) { + return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeId); } @Override @@ -159,54 +199,70 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService return alarmService.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds); } + @Override + public Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { + return alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, type); + } + @Override public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { return alarmService.findLatestByOriginatorAndType(tenantId, originator, type); } + @Deprecated private void onAlarmUpdated(AlarmOperationResult result) { wsCallBackExecutor.submit(() -> { - Alarm alarm = result.getAlarm(); - TenantId tenantId = result.getAlarm().getTenantId(); + AlarmInfo alarm = new AlarmInfo(result.getAlarm()); + TenantId tenantId = alarm.getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); + }); } }); } - private void onAlarmDeleted(AlarmOperationResult result) { + private void onAlarmUpdated(AlarmApiCallResult result) { wsCallBackExecutor.submit(() -> { - Alarm alarm = result.getAlarm(); - TenantId tenantId = result.getAlarm().getTenantId(); + AlarmInfo alarm = result.getAlarm(); + TenantId tenantId = alarm.getTenantId(); for (EntityId entityId : result.getPropagatedEntitiesList()) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); + }); } + notificationRuleProcessor.process(AlarmTrigger.builder() + .tenantId(tenantId) + .alarmUpdate(result) + .build()); }); } - private class AlarmUpdateCallback implements FutureCallback { + private void onAlarmDeleted(AlarmApiCallResult result) { + wsCallBackExecutor.submit(() -> { + AlarmInfo alarm = result.getAlarm(); + TenantId tenantId = alarm.getTenantId(); + for (EntityId entityId : result.getPropagatedEntitiesList()) { + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); + }); + } + notificationRuleProcessor.process(AlarmTrigger.builder() + .tenantId(tenantId) + .alarmUpdate(result) + .build()); + }); + } + + private class AlarmUpdateCallback implements FutureCallback { @Override - public void onSuccess(@Nullable AlarmOperationResult result) { + public void onSuccess(@Nullable AlarmApiCallResult result) { onAlarmUpdated(result); } @@ -216,4 +272,31 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } } + private AlarmApiCallResult withWsCallback(AlarmApiCallResult result) { + return withWsCallback(null, result); + } + + private AlarmApiCallResult withWsCallback(AlarmModificationRequest request, AlarmApiCallResult result) { + if (result.isSuccessful() && result.isModified()) { + Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor); + if (result.isSeverityChanged()) { + AlarmInfo alarm = result.getAlarm(); + AlarmComment.AlarmCommentBuilder alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", + String.format("Alarm severity was updated from %s to %s", result.getOldSeverity(), alarm.getSeverity()))); + if (request != null && request.getUserId() != null) { + alarmComment.userId(request.getUserId()); + } + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment.build(), null); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + } + } + return result; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 667ad5b881..ffa6d7bf48 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,13 +40,11 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; -import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; @@ -85,11 +83,8 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer public DefaultTelemetrySubscriptionService(AttributesService attrService, TimeseriesService tsService, @Lazy TbEntityViewService tbEntityViewService, - TbClusterService clusterService, - PartitionService partitionService, TbApiUsageReportClient apiUsageClient, TbApiUsageStateService apiUsageStateService) { - super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.tbEntityViewService = tbEntityViewService; @@ -375,73 +370,49 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes); + }); } private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice); + }); } private void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List ts) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts); + }); } private void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List keys, List ts) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); - if (currentPartitions.contains(tpi)) { - if (subscriptionManagerService.isPresent()) { - List updated = new ArrayList<>(); - List deleted = new ArrayList<>(); - - ts.stream().filter(Objects::nonNull).forEach(res -> { - if (res.isRemoved()) { - if (res.getData() != null) { - updated.add(res.getData()); - } else { - deleted.add(res.getKey()); - } + forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { + List updated = new ArrayList<>(); + List deleted = new ArrayList<>(); + + ts.stream().filter(Objects::nonNull).forEach(res -> { + if (res.isRemoved()) { + if (res.getData() != null) { + updated.add(res.getData()); + } else { + deleted.add(res.getKey()); } - }); + } + }); - subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY); - subscriptionManagerService.get().onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY); - } else { - log.warn("Possible misconfiguration because subscriptionManagerService is null!"); - } - } else { - TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys); - clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); - } + subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY); + subscriptionManagerService.onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY); + }, () -> { + return TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys); + }); } private void addVoidCallback(ListenableFuture saveFuture, final FutureCallback callback) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 298d894460..301376b16a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,4 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService { void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); - - } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java index db022966f0..8ec3b31b94 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java index 56d1785d45..d0af1080d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java b/application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java index e4c96ce653..940459571c 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java index 0228743076..b87758ace5 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index f8b42ecc1e..0c0ff3e0c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; @@ -95,6 +94,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -290,6 +290,7 @@ public class DefaultTransportApiService implements TransportApiService { device.setType(requestMsg.getDeviceType()); device.setCustomerId(gateway.getCustomerId()); DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); + device.setDeviceProfileId(deviceProfile.getId()); ObjectNode additionalInfo = JacksonUtil.newObjectNode(); additionalInfo.put(DataConstants.LAST_CONNECTED_GATEWAY, gatewayId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreToTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreToTransportService.java index 5f37758f58..ad4e09a1d6 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreToTransportService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreToTransportService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java index 323ca2528c..0cf251da1d 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java index debb0ec3f1..4247867e99 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java index 6fa0e58a6f..33b2b2a751 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java index c01b894fda..2855ddf8fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java index e178055706..ea059fcb2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java index 11b1751c0d..6d3cbf937e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index a56b1963d6..77d59fe069 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java index 3dc66b6210..77efc6555a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java new file mode 100644 index 0000000000..65b04ef0ab --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ttl; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.dao.notification.NotificationRequestDao; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; +import org.thingsboard.server.queue.discovery.PartitionService; + +import java.util.concurrent.TimeUnit; + +import static org.thingsboard.server.dao.model.ModelConstants.NOTIFICATION_TABLE_NAME; + +@Service +@ConditionalOnExpression("${sql.ttl.notifications.enabled:true} && ${sql.ttl.notifications.ttl:0} > 0") +@Slf4j +public class NotificationsCleanUpService extends AbstractCleanUpService { + + private final SqlPartitioningRepository partitioningRepository; + private final NotificationRequestDao notificationRequestDao; + + @Value("${sql.ttl.notifications.ttl:2592000}") + private long ttlInSec; + @Value("${sql.notifications.partition_size:168}") + private int partitionSizeInHours; + + public NotificationsCleanUpService(PartitionService partitionService, SqlPartitioningRepository partitioningRepository, + NotificationRequestDao notificationRequestDao) { + super(partitionService); + this.partitioningRepository = partitioningRepository; + this.notificationRequestDao = notificationRequestDao; + } + + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.notifications.checking_interval_ms:86400000})}", + fixedDelayString = "${sql.ttl.notifications.checking_interval_ms:86400000}") + public void cleanUp() { + long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec); + long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); + if (!isSystemTenantPartitionMine()) { + partitioningRepository.cleanupPartitionsCache(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); + return; + } + + long lastRemovedNotificationTs = partitioningRepository.dropPartitionsBefore(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); + if (lastRemovedNotificationTs > 0) { + long gap = TimeUnit.MINUTES.toMillis(10); + long requestExpTime = lastRemovedNotificationTs - TimeUnit.SECONDS.toMillis(NotificationRequestConfig.MAX_SENDING_DELAY) - gap; + // TODO: double-check this + notificationRequestDao.removeAllByCreatedTimeBefore(requestExpTime); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java index 00b869dede..25e2e8d289 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java index 1c21cc6029..7db4585acd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java index bc1e04f277..c6a80efc53 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,15 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.info.BuildProperties; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.UpdateMessage; +import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.util.TbCoreComponent; import javax.annotation.PostConstruct; @@ -53,6 +57,12 @@ public class DefaultUpdateService implements UpdateService { @Value("${updates.enabled}") private boolean updatesEnabled; + @Autowired(required = false) + private BuildProperties buildProperties; + + @Autowired + private NotificationRuleProcessor notificationRuleProcessor; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service")); private ScheduledFuture checkUpdatesFuture = null; @@ -66,14 +76,11 @@ public class DefaultUpdateService implements UpdateService { @PostConstruct private void init() { - updateMessage = new UpdateMessage("", false); + version = buildProperties != null ? buildProperties.getVersion() : "unknown"; + updateMessage = new UpdateMessage(false, version, "", "", "", ""); if (updatesEnabled) { try { platform = System.getProperty("platform", "unknown"); - version = getClass().getPackage().getImplementationVersion(); - if (version == null) { - version = "unknown"; - } instanceId = parseInstanceId(); checkUpdatesFuture = scheduler.scheduleAtFixedRate(checkUpdatesRunnable, 0, 1, TimeUnit.HOURS); } catch (Exception e) { @@ -121,11 +128,13 @@ public class DefaultUpdateService implements UpdateService { request.put(PLATFORM_PARAM, platform); request.put(VERSION_PARAM, version); request.put(INSTANCE_ID_PARAM, instanceId.toString()); - JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL+"/api/thingsboard/updates", request, JsonNode.class); - updateMessage = new UpdateMessage( - response.get("message").asText(), - response.get("updateAvailable").asBoolean() - ); + UpdateMessage prevUpdateMessage = updateMessage; + updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class); + if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) { + notificationRuleProcessor.process(NewPlatformVersionTrigger.builder() + .message(updateMessage) + .build()); + } } catch (Exception e) { log.trace(e.getMessage()); } diff --git a/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java index fce051dfde..370634564c 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java similarity index 72% rename from application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java rename to application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 06de7a5fe7..21d69d6b42 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DataConstants; @@ -48,6 +48,7 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.util.TenantRateLimitException; +import org.thingsboard.server.exception.UnauthorizedException; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.AccessValidator; @@ -56,26 +57,29 @@ import org.thingsboard.server.service.security.ValidationResult; import org.thingsboard.server.service.security.ValidationResultCode; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import org.thingsboard.server.service.subscription.TbAttributeSubscription; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbTimeseriesSubscription; -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; -import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; -import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; +import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; +import org.thingsboard.server.service.ws.notification.cmd.WsCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TelemetryPluginCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -95,6 +99,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -104,7 +111,8 @@ import java.util.stream.Collectors; @Service @TbCoreComponent @Slf4j -public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService { +@RequiredArgsConstructor +public class DefaultWebSocketService implements WebSocketService { public static final int NUMBER_OF_PING_ATTEMPTS = 3; @@ -112,7 +120,6 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi private static final Aggregation DEFAULT_AGGREGATION = Aggregation.NONE; private static final int UNKNOWN_SUBSCRIPTION_ID = 0; private static final String PROCESSING_MSG = "[{}] Processing: {}"; - private static final ObjectMapper jsonMapper = new ObjectMapper(); private static final String FAILED_TO_FETCH_DATA = "Failed to fetch data!"; private static final String FAILED_TO_FETCH_ATTRIBUTES = "Failed to fetch attributes!"; private static final String SESSION_META_DATA_NOT_FOUND = "Session meta-data not found!"; @@ -120,50 +127,59 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi private final ConcurrentMap wsSessionsMap = new ConcurrentHashMap<>(); - @Autowired - private TbLocalSubscriptionService oldSubService; - - @Autowired - private TbEntityDataSubscriptionService entityDataSubService; - - @Autowired - private TelemetryWebSocketMsgEndpoint msgEndpoint; - - @Autowired - private AccessValidator accessValidator; - - @Autowired - private AttributesService attributesService; - - @Autowired - private TimeseriesService tsService; - - @Autowired - private TbServiceInfoProvider serviceInfoProvider; - - @Autowired - private TbTenantProfileCache tenantProfileCache; + private final TbLocalSubscriptionService oldSubService; + private final TbEntityDataSubscriptionService entityDataSubService; + private final NotificationCommandsHandler notificationCmdsHandler; + private final WebSocketMsgEndpoint msgEndpoint; + private final AccessValidator accessValidator; + private final AttributesService attributesService; + private final TimeseriesService tsService; + private final TbServiceInfoProvider serviceInfoProvider; + private final TbTenantProfileCache tenantProfileCache; @Value("${server.ws.ping_timeout:30000}") private long pingTimeout; - private ConcurrentMap> tenantSubscriptionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> customerSubscriptionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> regularUserSubscriptionsMap = new ConcurrentHashMap<>(); - private ConcurrentMap> publicUserSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> customerSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> regularUserSubscriptionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> publicUserSubscriptionsMap = new ConcurrentHashMap<>(); private ExecutorService executor; + private ScheduledExecutorService pingExecutor; private String serviceId; - private ScheduledExecutorService pingExecutor; + private List> telemetryCmdsHandlers; + private List> notificationCmdsHandlers; @PostConstruct - public void initExecutor() { + public void init() { serviceId = serviceInfoProvider.getServiceId(); executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass()); pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping")); pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS); + + telemetryCmdsHandlers = List.of( + newCmdsHandler(TelemetryPluginCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd) + ); + notificationCmdsHandlers = List.of( + newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), + newCmdHandler(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), + newCmdHandler(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), + newCmdHandler(NotificationCmdsWrapper::getMarkAllAsReadCmd, notificationCmdsHandler::handleMarkAllAsReadCmd), + newCmdHandler(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd) + ); } @PreDestroy @@ -178,7 +194,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } @Override - public void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) { + public void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) { String sessionId = sessionRef.getSessionId(); log.debug(PROCESSING_MSG, sessionId, event); switch (event.getEventType()) { @@ -198,49 +214,19 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } @Override - public void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg) { + public void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg) { if (log.isTraceEnabled()) { log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg); } try { - TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class); - if (cmdsWrapper != null) { - if (cmdsWrapper.getAttrSubCmds() != null) { - cmdsWrapper.getAttrSubCmds().forEach(cmd -> { - if (processSubscription(sessionRef, cmd)) { - handleWsAttributesSubscriptionCmd(sessionRef, cmd); - } - }); - } - if (cmdsWrapper.getTsSubCmds() != null) { - cmdsWrapper.getTsSubCmds().forEach(cmd -> { - if (processSubscription(sessionRef, cmd)) { - handleWsTimeseriesSubscriptionCmd(sessionRef, cmd); - } - }); - } - if (cmdsWrapper.getHistoryCmds() != null) { - cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityDataCmds() != null) { - cmdsWrapper.getEntityDataCmds().forEach(cmd -> handleWsEntityDataCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getAlarmDataCmds() != null) { - cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityCountCmds() != null) { - cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) { - cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) { - cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } - if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) { - cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); - } + switch (sessionRef.getSessionType()) { + case TELEMETRY: + processTelemetryCmds(sessionRef, msg); + break; + case NOTIFICATIONS: + processNotificationCmds(sessionRef, msg); + break; } } catch (IOException e) { log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); @@ -248,7 +234,38 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsEntityDataCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { + private void processTelemetryCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { + TelemetryPluginCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, TelemetryPluginCmdsWrapper.class); + if (cmdsWrapper == null) { + return; + } + for (WsCmdListHandler cmdHandler : telemetryCmdsHandlers) { + List cmds = cmdHandler.extractCmds(cmdsWrapper); + if (cmds != null) { + cmdHandler.handle(sessionRef, cmds); + } + } + } + + private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException { + NotificationCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class); + for (WsCmdHandler cmdHandler : notificationCmdsHandlers) { + WsCmd cmd = cmdHandler.extractCmd(cmdsWrapper); + if (cmd != null) { + String sessionId = sessionRef.getSessionId(); + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { + try { + cmdHandler.handle(sessionRef, cmd); + } catch (Exception e) { + log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId, + sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e); + } + } + } + } + } + + private void handleWsEntityDataCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -258,7 +275,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { + private void handleWsEntityCountCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -268,7 +285,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { + private void handleWsAlarmDataCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -278,7 +295,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsDataUnsubscribeCmd(TelemetryWebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { + private void handleWsDataUnsubscribeCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -287,6 +304,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } + private void handleWsAlarmCountCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { + String sessionId = sessionRef.getSessionId(); + log.debug("[{}] Processing: {}", sessionId, cmd); + + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId) + && validateSubscriptionCmd(sessionRef, cmd)) { + entityDataSubService.handleCmd(sessionRef, cmd); + } + } + @Override public void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update) { sendWsMsg(sessionId, update.getSubscriptionId(), update); @@ -316,7 +343,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) { + private void processSessionClose(WebSocketSessionRef sessionRef) { var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); if (tenantProfileConfiguration != null) { String sessionId = "[" + sessionRef.getSessionId() + "]"; @@ -350,7 +377,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private boolean processSubscription(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) { + private boolean processSubscription(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) { var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); if (tenantProfileConfiguration == null) return true; @@ -422,7 +449,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private void handleWsAttributesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) { + private void handleWsAttributesSubscriptionCmd(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) { + if (!processSubscription(sessionRef, cmd)) { + return; + } + String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -443,14 +474,13 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef, + private void handleWsAttributesSubscriptionByKeys(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId, List keys) { FutureCallback> callback = new FutureCallback<>() { @Override public void onSuccess(List data) { List attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); Map subState = new HashMap<>(keys.size()); keys.forEach(key -> subState.put(key, 0L)); @@ -458,6 +488,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope()); + Lock subLock = new ReentrantLock(); TbAttributeSubscription sub = TbAttributeSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) @@ -467,9 +498,24 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi .allKeys(false) .keyStates(subState) .scope(scope) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> { + subLock.lock(); + try { + sendWsMsg(subscription.getSessionId(), update); + } finally { + subLock.unlock(); + } + }) .build(); - oldSubService.addSubscription(sub); + + subLock.lock(); + try{ + oldSubService.addSubscription(sub); + sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); + } finally { + subLock.unlock(); + } + } @Override @@ -494,7 +540,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsHistoryCmd(TelemetryWebSocketSessionRef sessionRef, GetHistoryCmd cmd) { + private void handleWsHistoryCmd(WebSocketSessionRef sessionRef, GetHistoryCmd cmd) { String sessionId = sessionRef.getSessionId(); WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); if (sessionMD == null) { @@ -544,19 +590,19 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi on(r -> Futures.addCallback(tsService.findAll(sessionRef.getSecurityCtx().getTenantId(), entityId, queries), callback, executor), callback::onFailure)); } - private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef, + private void handleWsAttributesSubscription(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) { FutureCallback> callback = new FutureCallback<>() { @Override public void onSuccess(List data) { List attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); Map subState = new HashMap<>(attributesData.size()); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope()); + Lock subLock = new ReentrantLock(); TbAttributeSubscription sub = TbAttributeSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) @@ -565,9 +611,24 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi .entityId(entityId) .allKeys(true) .keyStates(subState) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) - .scope(scope).build(); - oldSubService.addSubscription(sub); + .updateProcessor((subscription, update) -> { + subLock.lock(); + try { + sendWsMsg(subscription.getSessionId(), update); + } finally { + subLock.unlock(); + } + }) + .scope(scope) + .build(); + + subLock.lock(); + try { + oldSubService.addSubscription(sub); + sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); + } finally { + subLock.unlock(); + } } @Override @@ -587,7 +648,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsTimeseriesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) { + private void handleWsTimeseriesSubscriptionCmd(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) { + if (!processSubscription(sessionRef, cmd)) { + return; + } + String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); @@ -607,7 +672,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsTimeseriesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef, + private void handleWsTimeseriesSubscriptionByKeys(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) { long startTs; if (cmd.getTimeWindow() > 0) { @@ -631,25 +696,40 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void handleWsTimeseriesSubscription(TelemetryWebSocketSessionRef sessionRef, + private void handleWsTimeseriesSubscription(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) { FutureCallback> callback = new FutureCallback>() { @Override public void onSuccess(List data) { - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); Map subState = new HashMap<>(data.size()); data.forEach(v -> subState.put(v.getKey(), v.getTs())); + Lock subLock = new ReentrantLock(); TbTimeseriesSubscription sub = TbTimeseriesSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) .subscriptionId(cmd.getCmdId()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> { + subLock.lock(); + try { + sendWsMsg(subscription.getSessionId(), update); + } finally { + subLock.unlock(); + } + }) .allKeys(true) - .keyStates(subState).build(); - oldSubService.addSubscription(sub); + .keyStates(subState) + .build(); + + subLock.lock(); + try { + oldSubService.addSubscription(sub); + sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); + } finally { + subLock.unlock(); + } } @Override @@ -669,25 +749,40 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure)); } - private FutureCallback> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List keys) { + private FutureCallback> getSubscriptionCallback(final WebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List keys) { return new FutureCallback<>() { @Override public void onSuccess(List data) { - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); Map subState = new HashMap<>(keys.size()); keys.forEach(key -> subState.put(key, startTs)); data.forEach(v -> subState.put(v.getKey(), v.getTs())); + Lock subLock = new ReentrantLock(); TbTimeseriesSubscription sub = TbTimeseriesSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) .subscriptionId(cmd.getCmdId()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) - .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg) + .updateProcessor((subscription, update) -> { + subLock.lock(); + try { + sendWsMsg(subscription.getSessionId(), update); + } finally { + subLock.unlock(); + } + }) .allKeys(false) - .keyStates(subState).build(); - oldSubService.addSubscription(sub); + .keyStates(subState) + .build(); + + subLock.lock(); + try{ + oldSubService.addSubscription(sub); + sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); + } finally { + subLock.unlock(); + } } @Override @@ -704,7 +799,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi }; } - private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { + private void unsubscribe(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { oldSubService.cancelAllSessionSubscriptions(sessionId); } else { @@ -712,7 +807,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { if (cmd.getCmdId() < 0) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); @@ -727,7 +822,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { if (cmd.getCmdId() < 0) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); @@ -741,7 +836,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { if (cmd.getCmdId() < 0) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); @@ -756,7 +851,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Device id is empty!"); @@ -766,11 +861,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return true; } - private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { + private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { return validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId); } - private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, int cmdId, String sessionId) { + private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, int cmdId, String sessionId) { WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); if (sessionMD == null) { log.warn("[{}] Session meta data not found. ", sessionId); @@ -783,17 +878,27 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } - private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, EntityDataUpdate update) { + private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { + if (cmd.getCmdId() < 0) { + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, + "Cmd id is negative value!"); + sendWsMsg(sessionRef, update); + return false; + } + return true; + } + + private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) { sendWsMsg(sessionRef, update.getCmdId(), update); } - private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) { + private void sendWsMsg(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) { sendWsMsg(sessionRef, update.getSubscriptionId(), update); } - private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, int cmdId, Object update) { + private void sendWsMsg(WebSocketSessionRef sessionRef, int cmdId, Object update) { try { - String msg = jsonMapper.writeValueAsString(update); + String msg = JacksonUtil.OBJECT_MAPPER.writeValueAsString(update); executor.submit(() -> { try { msgEndpoint.send(sessionRef, cmdId, msg); @@ -935,9 +1040,52 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return limit == 0 ? DEFAULT_LIMIT : limit; } - private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) { + private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) { return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId())) .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); } + + public static WsCmdHandler newCmdHandler(java.util.function.Function cmdExtractor, + BiConsumer handler) { + return new WsCmdHandler<>(cmdExtractor, handler); + } + + public static WsCmdListHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, + BiConsumer handler) { + return new WsCmdListHandler<>(cmdsExtractor, handler); + } + + @RequiredArgsConstructor + public static class WsCmdHandler { + private final java.util.function.Function cmdExtractor; + private final BiConsumer handler; + + public C extractCmd(W cmdsWrapper) { + return cmdExtractor.apply(cmdsWrapper); + } + + @SuppressWarnings("unchecked") + public void handle(WebSocketSessionRef sessionRef, Object cmd) { + handler.accept(sessionRef, (C) cmd); + } + } + + @RequiredArgsConstructor + public static class WsCmdListHandler { + private final java.util.function.Function> cmdsExtractor; + private final BiConsumer handler; + + public List extractCmds(W cmdsWrapper) { + return cmdsExtractor.apply(cmdsWrapper); + } + + @SuppressWarnings("unchecked") + public void handle(WebSocketSessionRef sessionRef, List cmds) { + cmds.forEach(cmd -> { + handler.accept(sessionRef, (C) cmd); + }); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java b/application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java rename to application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java index 527f5dc667..591a688809 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import lombok.Getter; import lombok.ToString; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java similarity index 59% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java rename to application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java index d71777ae49..dd54b01580 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import org.springframework.web.socket.CloseStatus; +import org.thingsboard.server.service.ws.WebSocketSessionRef; import java.io.IOException; /** * Created by ashvayka on 27.03.18. */ -public interface TelemetryWebSocketMsgEndpoint { +public interface WebSocketMsgEndpoint { - void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException; + void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException; - void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException; + void sendPing(WebSocketSessionRef sessionRef, long currentTime) throws IOException; - void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException; + void close(WebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java rename to application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java index 510283b31d..a31c6a0ca0 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; import org.springframework.web.socket.CloseStatus; -import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate; -import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; +import org.thingsboard.server.service.ws.SessionEvent; +import org.thingsboard.server.service.ws.WebSocketSessionRef; /** * Created by ashvayka on 27.03.18. */ -public interface TelemetryWebSocketService { +public interface WebSocketService { - void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent sessionEvent); + void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent); - void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg); + void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg); void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java similarity index 67% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java rename to application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java index da8a8962e6..e799c8faa0 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; +import lombok.Builder; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.thingsboard.server.service.security.model.SecurityUser; import java.net.InetSocketAddress; @@ -25,34 +27,25 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Created by ashvayka on 27.03.18. */ -public class TelemetryWebSocketSessionRef { +@RequiredArgsConstructor +@Builder +@Getter +public class WebSocketSessionRef { private static final long serialVersionUID = 1L; - @Getter private final String sessionId; - @Getter private final SecurityUser securityCtx; - @Getter private final InetSocketAddress localAddress; - @Getter private final InetSocketAddress remoteAddress; - @Getter - private final AtomicInteger sessionSubIdSeq; - - public TelemetryWebSocketSessionRef(String sessionId, SecurityUser securityCtx, InetSocketAddress localAddress, InetSocketAddress remoteAddress) { - this.sessionId = sessionId; - this.securityCtx = securityCtx; - this.localAddress = localAddress; - this.remoteAddress = remoteAddress; - this.sessionSubIdSeq = new AtomicInteger(); - } + private final WebSocketSessionType sessionType; + private final AtomicInteger sessionSubIdSeq = new AtomicInteger(); @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - TelemetryWebSocketSessionRef that = (TelemetryWebSocketSessionRef) o; + WebSocketSessionRef that = (WebSocketSessionRef) o; return Objects.equals(sessionId, that.sessionId); } @@ -63,10 +56,11 @@ public class TelemetryWebSocketSessionRef { @Override public String toString() { - return "TelemetryWebSocketSessionRef{" + + return "WebSocketSessionRef{" + "sessionId='" + sessionId + '\'' + ", localAddress=" + localAddress + ", remoteAddress=" + remoteAddress + + ", sessionType=" + sessionType + '}'; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java new file mode 100644 index 0000000000..5c1a155c4b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Optional; + +@RequiredArgsConstructor +@Getter +public enum WebSocketSessionType { + TELEMETRY("telemetry"), + NOTIFICATIONS("notifications"); + + private final String name; + + public static Optional forName(String name) { + return Arrays.stream(values()) + .filter(sessionType -> sessionType.getName().equals(name)) + .findFirst(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java b/application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java similarity index 77% rename from application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java rename to application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java index ab353c0581..48c2e65509 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws; /** * Created by ashvayka on 27.03.18. */ public class WsSessionMetaData { - private TelemetryWebSocketSessionRef sessionRef; + private WebSocketSessionRef sessionRef; private long lastActivityTime; - public WsSessionMetaData(TelemetryWebSocketSessionRef sessionRef) { + public WsSessionMetaData(WebSocketSessionRef sessionRef) { super(); this.sessionRef = sessionRef; this.lastActivityTime = System.currentTimeMillis(); } - public TelemetryWebSocketSessionRef getSessionRef() { + public WebSocketSessionRef getSessionRef() { return sessionRef; } - public void setSessionRef(TelemetryWebSocketSessionRef sessionRef) { + public void setSessionRef(WebSocketSessionRef sessionRef) { this.sessionRef = sessionRef; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java new file mode 100644 index 0000000000..012808b253 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -0,0 +1,248 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.common.data.id.IdBased; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; +import org.thingsboard.server.service.ws.WebSocketService; +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription; +import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +@Slf4j +public class DefaultNotificationCommandsHandler implements NotificationCommandsHandler { + + private final NotificationService notificationService; + private final TbLocalSubscriptionService localSubscriptionService; + private final NotificationCenter notificationCenter; + private final TbServiceInfoProvider serviceInfoProvider; + @Autowired @Lazy + private WebSocketService wsService; + + @Override + public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) { + log.debug("[{}] Handling unread notifications subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId()); + SecurityUser securityCtx = sessionRef.getSecurityCtx(); + NotificationsSubscription subscription = NotificationsSubscription.builder() + .serviceId(serviceInfoProvider.getServiceId()) + .sessionId(sessionRef.getSessionId()) + .subscriptionId(cmd.getCmdId()) + .tenantId(securityCtx.getTenantId()) + .entityId(securityCtx.getId()) + .updateProcessor(this::handleNotificationsSubscriptionUpdate) + .limit(cmd.getLimit()) + .build(); + localSubscriptionService.addSubscription(subscription); + + fetchUnreadNotifications(subscription); + sendUpdate(sessionRef.getSessionId(), subscription.createFullUpdate()); + } + + @Override + public void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd) { + log.debug("[{}] Handling unread notifications count subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId()); + SecurityUser securityCtx = sessionRef.getSecurityCtx(); + NotificationsCountSubscription subscription = NotificationsCountSubscription.builder() + .serviceId(serviceInfoProvider.getServiceId()) + .sessionId(sessionRef.getSessionId()) + .subscriptionId(cmd.getCmdId()) + .tenantId(securityCtx.getTenantId()) + .entityId(securityCtx.getId()) + .updateProcessor(this::handleNotificationsCountSubscriptionUpdate) + .build(); + localSubscriptionService.addSubscription(subscription); + + fetchUnreadNotificationsCount(subscription); + sendUpdate(sessionRef.getSessionId(), subscription.createUpdate()); + } + + private void fetchUnreadNotifications(NotificationsSubscription subscription) { + log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); + PageData notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(), + (UserId) subscription.getEntityId(), subscription.getLimit()); + subscription.getLatestUnreadNotifications().clear(); + notifications.getData().forEach(notification -> { + subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); + }); + subscription.getTotalUnreadCounter().set((int) notifications.getTotalElements()); + } + + private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) { + log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId()); + int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId()); + subscription.getUnreadCounter().set(unreadCount); + } + + + /* Notifications subscription update handling */ + private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { + try { + if (subscriptionUpdate.getNotificationUpdate() != null) { + handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); + } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { + handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); + } + } catch (Exception e) { + log.error("[{}, subId: {}] Failed to handle update for notifications subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e); + } + } + + private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { + log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); + Notification notification = update.getNotification(); + UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId(); + if (update.isCreated()) { + subscription.getLatestUnreadNotifications().put(notificationId, notification); + subscription.getTotalUnreadCounter().incrementAndGet(); + if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) { + Set beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit()) + .map(IdBased::getUuidId).collect(Collectors.toSet()); + beyondLimit.forEach(id -> subscription.getLatestUnreadNotifications().remove(id)); + } + sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + } else if (update.isUpdated()) { + if (update.getNewStatus() == NotificationStatus.READ) { + if (update.isAllNotifications() || subscription.getLatestUnreadNotifications().containsKey(notificationId)) { + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } else { + subscription.getTotalUnreadCounter().decrementAndGet(); + sendUpdate(subscription.getSessionId(), subscription.createCountUpdate()); + } + } else if (notification.getStatus() != NotificationStatus.READ) { + if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) { + subscription.getLatestUnreadNotifications().put(notificationId, notification); + sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); + } + } + } else if (update.isDeleted()) { + if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) { + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } else if (notification.getStatus() != NotificationStatus.READ) { + subscription.getTotalUnreadCounter().decrementAndGet(); + sendUpdate(subscription.getSessionId(), subscription.createCountUpdate()); + } + } + } + + private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) { + log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); + fetchUnreadNotifications(subscription); + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); + } + + + /* Notifications count subscription update handling */ + private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { + try { + if (subscriptionUpdate.getNotificationUpdate() != null) { + handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); + } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { + handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); + } + } catch (Exception e) { + log.error("[{}, subId: {}] Failed to handle update for notifications count subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e); + } + } + + private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) { + log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); + if (update.isCreated()) { + subscription.getUnreadCounter().incrementAndGet(); + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } else if (update.isUpdated()) { + if (update.getNewStatus() == NotificationStatus.READ) { + if (update.isAllNotifications()) { + fetchUnreadNotificationsCount(subscription); + } else { + subscription.getUnreadCounter().decrementAndGet(); + } + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } + } else if (update.isDeleted()) { + if (update.getNotification().getStatus() != NotificationStatus.READ) { + subscription.getUnreadCounter().decrementAndGet(); + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } + } + } + + private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) { + log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); + fetchUnreadNotificationsCount(subscription); + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); + } + + + @Override + public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd) { + SecurityUser securityCtx = sessionRef.getSecurityCtx(); + cmd.getNotifications().stream() + .map(NotificationId::new) + .forEach(notificationId -> { + notificationCenter.markNotificationAsRead(securityCtx.getTenantId(), securityCtx.getId(), notificationId); + }); + } + + @Override + public void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd) { + SecurityUser securityCtx = sessionRef.getSecurityCtx(); + notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), securityCtx.getId()); + } + + @Override + public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId()); + } + + private void sendUpdate(String sessionId, CmdUpdate update) { + log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update); + wsService.sendWsMsg(sessionId, update); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java new file mode 100644 index 0000000000..2b27166106 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification; + +import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; + +public interface NotificationCommandsHandler { + + void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd); + + void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd); + + void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd); + + void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd); + + void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java new file mode 100644 index 0000000000..f096106135 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MarkAllNotificationsAsReadCmd implements WsCmd { + private int cmdId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java new file mode 100644 index 0000000000..55de75387f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MarkNotificationsAsReadCmd implements WsCmd { + private int cmdId; + private List notifications; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java new file mode 100644 index 0000000000..88613eb966 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.Data; + +@Data +public class NotificationCmdsWrapper { + + private NotificationsCountSubCmd unreadCountSubCmd; + + private NotificationsSubCmd unreadSubCmd; + + private MarkNotificationsAsReadCmd markAsReadCmd; + + private MarkAllNotificationsAsReadCmd markAllAsReadCmd; + + private NotificationsUnsubCmd unsubCmd; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java new file mode 100644 index 0000000000..0d6757a6e7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NotificationsCountSubCmd implements WsCmd { + private int cmdId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java new file mode 100644 index 0000000000..e022d2c49f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NotificationsSubCmd implements WsCmd { + private int cmdId; + private int limit; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java new file mode 100644 index 0000000000..c9f0897eeb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NotificationsUnsubCmd implements UnsubscribeCmd, WsCmd { + private int cmdId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java new file mode 100644 index 0000000000..93f51a965b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; + +@Getter +@ToString +public class UnreadNotificationsCountUpdate extends CmdUpdate { + + private final int totalUnreadCount; + + @Builder + @JsonCreator + public UnreadNotificationsCountUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, + @JsonProperty("errorMsg") String errorMsg, + @JsonProperty("totalUnreadCount") int totalUnreadCount) { + super(cmdId, errorCode, errorMsg); + this.totalUnreadCount = totalUnreadCount; + } + + @Override + public CmdUpdateType getCmdUpdateType() { + return CmdUpdateType.NOTIFICATIONS_COUNT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java new file mode 100644 index 0000000000..e64624d16e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; + +import java.util.Collection; + +@Getter +@ToString(exclude = "notifications") +public class UnreadNotificationsUpdate extends CmdUpdate { + + private final Collection notifications; + private final Notification update; + private final int totalUnreadCount; + + @Builder + @JsonCreator + public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, + @JsonProperty("errorMsg") String errorMsg, + @JsonProperty("notifications") Collection notifications, + @JsonProperty("update") Notification update, + @JsonProperty("totalUnreadCount") int totalUnreadCount) { + super(cmdId, errorCode, errorMsg); + this.notifications = notifications; + this.update = update; + this.totalUnreadCount = totalUnreadCount; + } + + @Override + public CmdUpdateType getCmdUpdateType() { + return CmdUpdateType.NOTIFICATIONS; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java new file mode 100644 index 0000000000..97bfeab70c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +public interface WsCmd { + int getCmdId(); +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java new file mode 100644 index 0000000000..ebbcc716ab --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.sub; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.NotificationRequestId; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NotificationRequestUpdate { + private NotificationRequestId notificationRequestId; + private boolean deleted; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java new file mode 100644 index 0000000000..b00231a5c9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.sub; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; + +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NotificationUpdate { + + private UUID notificationId; + + private boolean created; + private Notification notification; + + private boolean updated; + private NotificationStatus newStatus; + private boolean allNotifications; + + private boolean deleted; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java new file mode 100644 index 0000000000..cb5d140d21 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.sub; + +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.subscription.TbSubscription; +import org.thingsboard.server.service.subscription.TbSubscriptionType; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +@Getter +public class NotificationsCountSubscription extends TbSubscription { + + private final AtomicInteger unreadCounter = new AtomicInteger(); + + @Builder + public NotificationsCountSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, + BiConsumer updateProcessor) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS_COUNT, updateProcessor); + } + + public UnreadNotificationsCountUpdate createUpdate() { + return UnreadNotificationsCountUpdate.builder() + .cmdId(getSubscriptionId()) + .totalUnreadCount(unreadCounter.get()) + .build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java new file mode 100644 index 0000000000..591781a5f6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.sub; + +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.service.subscription.TbSubscription; +import org.thingsboard.server.service.subscription.TbSubscriptionType; +import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +@Getter +public class NotificationsSubscription extends TbSubscription { + + private final Map latestUnreadNotifications = new HashMap<>(); + private final int limit; + private final AtomicInteger totalUnreadCounter = new AtomicInteger(); + + @Builder + public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, + BiConsumer updateProcessor, + int limit) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor); + this.limit = limit; + } + + public UnreadNotificationsUpdate createFullUpdate() { + return UnreadNotificationsUpdate.builder() + .cmdId(getSubscriptionId()) + .notifications(getSortedNotifications()) + .totalUnreadCount(totalUnreadCounter.get()) + .build(); + } + + public List getSortedNotifications() { + return latestUnreadNotifications.values().stream() + .sorted(Comparator.comparing(BaseData::getCreatedTime, Comparator.reverseOrder())) + .collect(Collectors.toList()); + } + + public UnreadNotificationsUpdate createPartialUpdate(Notification notification) { + return UnreadNotificationsUpdate.builder() + .cmdId(getSubscriptionId()) + .update(notification) + .totalUnreadCount(totalUnreadCounter.get()) + .build(); + } + + public UnreadNotificationsUpdate createCountUpdate() { + return UnreadNotificationsUpdate.builder() + .cmdId(getSubscriptionId()) + .totalUnreadCount(totalUnreadCounter.get()) + .build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java new file mode 100644 index 0000000000..b69039cbfe --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.sub; + +import lombok.Data; + +@Data +public class NotificationsSubscriptionUpdate { + + private final NotificationUpdate notificationUpdate; + private final NotificationRequestUpdate notificationRequestUpdate; + + public NotificationsSubscriptionUpdate(NotificationUpdate notificationUpdate) { + this.notificationUpdate = notificationUpdate; + this.notificationRequestUpdate = null; + } + + public NotificationsSubscriptionUpdate(NotificationRequestUpdate notificationRequestUpdate) { + this.notificationUpdate = null; + this.notificationRequestUpdate = notificationRequestUpdate; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java similarity index 88% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java index b0a63970a5..c5324208ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws.telemetry; /** * Created by ashvayka on 08.05.17. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java similarity index 76% rename from application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java index 44f2ea0943..6de98ac8a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry; +package org.thingsboard.server.service.ws.telemetry; import lombok.Data; +import org.thingsboard.server.service.ws.WebSocketSessionRef; /** * Created by ashvayka on 27.03.18. @@ -23,7 +24,7 @@ import lombok.Data; @Data public class TelemetryWebSocketTextMsg { - private final TelemetryWebSocketSessionRef sessionRef; + private final WebSocketSessionRef sessionRef; private final String payload; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java similarity index 52% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java index 0c46f90610..4daf2a939b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd; +package org.thingsboard.server.service.ws.telemetry.cmd; import lombok.Data; -import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd; import java.util.List; @@ -52,4 +54,8 @@ public class TelemetryPluginCmdsWrapper { private List entityCountUnsubscribeCmds; + private List alarmCountCmds; + + private List alarmCountUnsubscribeCmds; + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java similarity index 82% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java index f5e2630af8..1ccec261b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java index b74f2f7ea7..6791066641 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java index 0c912fac30..6a5f9d820d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; @NoArgsConstructor @AllArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TelemetryPluginCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TelemetryPluginCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java index 7423e9e4b0..dc9d3a48c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TelemetryPluginCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java similarity index 85% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java index 309a9297d6..f0a7b9d3af 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v1; +package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggHistoryCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggHistoryCmd.java index 423c55bd0d..b928363fc4 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggHistoryCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggKey.java similarity index 88% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggKey.java index d567149d01..dc8966c0d5 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggKey.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import org.thingsboard.server.common.data.kv.Aggregation; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggTimeSeriesCmd.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggTimeSeriesCmd.java index 1f4d88d2c3..f9dc34fb75 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AggTimeSeriesCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java new file mode 100644 index 0000000000..80fd998eb1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.telemetry.cmd.v2; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import org.thingsboard.server.common.data.query.AlarmCountQuery; + +public class AlarmCountCmd extends DataCmd { + + @Getter + private final AlarmCountQuery query; + + @JsonCreator + public AlarmCountCmd(@JsonProperty("cmdId") int cmdId, + @JsonProperty("query") AlarmCountQuery query) { + super(cmdId); + this.query = query; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java new file mode 100644 index 0000000000..79c860f3f6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.telemetry.cmd.v2; + +import lombok.Data; + +@Data +public class AlarmCountUnsubscribeCmd implements UnsubscribeCmd { + + private final int cmdId; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUpdate.java new file mode 100644 index 0000000000..b6f70811b8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUpdate.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.telemetry.cmd.v2; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; + +@ToString +public class AlarmCountUpdate extends CmdUpdate { + + @Getter + private int count; + + public AlarmCountUpdate(int cmdId, int count) { + super(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null); + this.count = count; + } + + public AlarmCountUpdate(int cmdId, int errorCode, String errorMsg) { + super(cmdId, errorCode, errorMsg); + } + + @Override + public CmdUpdateType getCmdUpdateType() { + return CmdUpdateType.ALARM_COUNT_DATA; + } + + @JsonCreator + public AlarmCountUpdate(@JsonProperty("cmdId") int cmdId, + @JsonProperty("count") int count, + @JsonProperty("errorCode") int errorCode, + @JsonProperty("errorMsg") String errorMsg) { + super(cmdId, errorCode, errorMsg); + this.count = count; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java index c2c01f8f77..0f83d00d6d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java index 153935b7a7..97db558cf9 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUpdate.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUpdate.java index afd9d12b16..7d7d29300a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -21,7 +21,7 @@ import lombok.Getter; import lombok.ToString; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdate.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdate.java index 075c80b92b..ba779176aa 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java similarity index 76% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java index cd78e760a6..f5b3809ce2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; public enum CmdUpdateType { ENTITY_DATA, ALARM_DATA, - COUNT_DATA + ALARM_COUNT_DATA, + COUNT_DATA, + NOTIFICATIONS, + NOTIFICATIONS_COUNT } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java similarity index 84% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java index b93af17d89..c09cbc03d1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import lombok.Getter; -import lombok.NoArgsConstructor; @Data public class DataCmd { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataUpdate.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataUpdate.java index eda60b87b4..c4bfce441a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Getter; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java index c71884df38..c39f81f1b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.EntityCountQuery; -import org.thingsboard.server.common.data.query.EntityDataQuery; public class EntityCountCmd extends DataCmd { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java index c8daf30833..b82fecd8fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUpdate.java similarity index 83% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUpdate.java index de61d0e5fb..030d39b8dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,17 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.ToString; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.query.EntityData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; - -import java.util.List; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; @ToString public class EntityCountUpdate extends CmdUpdate { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java index 8a187afa7b..5fe9179651 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java index 45aa0c5216..80577edda2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUpdate.java similarity index 91% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUpdate.java index 8da696ce9c..3b3cf69186 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -21,7 +21,7 @@ import lombok.Getter; import lombok.ToString; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; -import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityHistoryCmd.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityHistoryCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityHistoryCmd.java index 946775889f..5f22897b8f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityHistoryCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import org.thingsboard.server.common.data.kv.Aggregation; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/GetTsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/GetTsCmd.java similarity index 88% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/GetTsCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/GetTsCmd.java index 724c2d6fd2..5d6a730772 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/GetTsCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/GetTsCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import org.thingsboard.server.common.data.kv.Aggregation; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/LatestValueCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/LatestValueCmd.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/LatestValueCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/LatestValueCmd.java index 184026ac54..291829b47b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/LatestValueCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/LatestValueCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import org.thingsboard.server.common.data.query.EntityKey; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/TimeSeriesCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/TimeSeriesCmd.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/TimeSeriesCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/TimeSeriesCmd.java index daf9c32fbd..6ece880d7e 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/TimeSeriesCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/TimeSeriesCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java similarity index 85% rename from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java index eec564e7a8..81288c76da 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.cmd.v2; +package org.thingsboard.server.service.ws.telemetry.cmd.v2; public interface UnsubscribeCmd { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java similarity index 68% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java index 3f4bc9ce1e..90041ffa13 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.ws.telemetry.sub; import lombok.Getter; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.query.AlarmData; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; public class AlarmSubscriptionUpdate { @@ -36,15 +28,15 @@ public class AlarmSubscriptionUpdate { @Getter private String errorMsg; @Getter - private Alarm alarm; + private AlarmInfo alarm; @Getter private boolean alarmDeleted; - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) { + public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm) { this(subscriptionId, alarm, false); } - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) { + public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm, boolean alarmDeleted) { super(); this.subscriptionId = subscriptionId; this.alarm = alarm; @@ -64,7 +56,7 @@ public class AlarmSubscriptionUpdate { @Override public String toString() { - return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm=" - + alarm + "]"; + return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + + ", alarm=" + alarm + "]"; } -} +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionState.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/SubscriptionState.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionState.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/SubscriptionState.java index 1343527444..88d8f7ca7c 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionState.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/SubscriptionState.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.ws.telemetry.sub; import lombok.AllArgsConstructor; import lombok.Getter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/TelemetrySubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/TelemetrySubscriptionUpdate.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/telemetry/sub/TelemetrySubscriptionUpdate.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/TelemetrySubscriptionUpdate.java index cbbce43405..108c5cf1d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/TelemetrySubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/TelemetrySubscriptionUpdate.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.telemetry.sub; +package org.thingsboard.server.service.ws.telemetry.sub; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import java.util.ArrayList; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java index af03c72004..ed898ae999 100644 --- a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java +++ b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/utils/CsvUtils.java b/application/src/main/java/org/thingsboard/server/utils/CsvUtils.java index e4d0c13376..a40b41955d 100644 --- a/application/src/main/java/org/thingsboard/server/utils/CsvUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/CsvUtils.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java b/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java deleted file mode 100644 index d7e44e6e5a..0000000000 --- a/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.utils; - -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.function.Consumer; - -/** - * This class deduplicate executions of the specified function. - * Useful in cluster mode, when you get event about partition change multiple times. - * Assuming that the function execution is expensive, we should execute it immediately when first time event occurs and - * later, once the processing of first event is done, process last pending task. - * - * @param

parameters of the function - */ -@Slf4j -public class EventDeduplicationExecutor

{ - private final String name; - private final ExecutorService executor; - private final Consumer

function; - private P pendingTask; - private boolean busy; - - public EventDeduplicationExecutor(String name, ExecutorService executor, Consumer

function) { - this.name = name; - this.executor = executor; - this.function = function; - } - - public void submit(P params) { - log.info("[{}] Going to submit: {}", name, params); - synchronized (EventDeduplicationExecutor.this) { - if (!busy) { - busy = true; - pendingTask = null; - try { - log.info("[{}] Submitting task: {}", name, params); - executor.submit(() -> { - try { - log.info("[{}] Executing task: {}", name, params); - function.accept(params); - } catch (Throwable e) { - log.warn("[{}] Failed to process task with parameters: {}", name, params, e); - throw e; - } finally { - unlockAndProcessIfAny(); - } - }); - } catch (Throwable e) { - log.warn("[{}] Failed to submit task with parameters: {}", name, params, e); - unlockAndProcessIfAny(); - throw e; - } - } else { - log.info("[{}] Task is already in progress. {} pending task: {}", name, pendingTask == null ? "adding" : "updating", params); - pendingTask = params; - } - } - } - - private void unlockAndProcessIfAny() { - synchronized (EventDeduplicationExecutor.this) { - busy = false; - if (pendingTask != null) { - submit(pendingTask); - } - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java new file mode 100644 index 0000000000..bb2e49bd62 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java @@ -0,0 +1,114 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.utils; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.model.DDFFileParser; +import org.eclipse.leshan.core.model.DefaultDDFFileValidator; +import org.eclipse.leshan.core.model.InvalidDDFFileException; +import org.eclipse.leshan.core.model.ObjectModel; +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; +import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; +import org.thingsboard.server.dao.exception.DataValidationException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT; + +@Slf4j +public class LwM2mObjectModelUtils { + + private static final DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); + + public static void toLwm2mResource (TbResource resource) throws ThingsboardException { + try { + List objectModels = + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + if (!objectModels.isEmpty()) { + ObjectModel objectModel = objectModels.get(0); + + String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version; + String name = objectModel.name; + resource.setResourceKey(resourceKey); + if (resource.getId() == null) { + resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version); + } + resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); + } else { + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); + } + } catch (InvalidDDFFileException e) { + log.error("Failed to parse file {}", resource.getFileName(), e); + throw new DataValidationException("Failed to parse file " + resource.getFileName()); + } catch (IOException e) { + throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); + } + if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); + } + } + + public static LwM2mObject toLwM2mObject(TbResource resource, boolean isSave) { + try { + List objectModels = + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + if (objectModels.size() == 0) { + return null; + } else { + ObjectModel obj = objectModels.get(0); + LwM2mObject lwM2mObject = new LwM2mObject(); + lwM2mObject.setId(obj.id); + lwM2mObject.setKeyId(resource.getResourceKey()); + lwM2mObject.setName(obj.name); + lwM2mObject.setMultiple(obj.multiple); + lwM2mObject.setMandatory(obj.mandatory); + LwM2mInstance instance = new LwM2mInstance(); + instance.setId(0); + List resources = new ArrayList<>(); + obj.resources.forEach((k, v) -> { + if (isSave) { + LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); + resources.add(lwM2MResourceObserve); + } else if (v.operations.isReadable()) { + LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); + resources.add(lwM2MResourceObserve); + } + }); + if (isSave || resources.size() > 0) { + instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); + lwM2mObject.setInstances(new LwM2mInstance[]{instance}); + return lwM2mObject; + } else { + return null; + } + } + } catch (IOException | InvalidDDFFileException e) { + log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e); + return null; + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index fbb217a846..2fc0fa134c 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java b/application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java index b521b6f17b..3bf1efc8f4 100644 --- a/application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java +++ b/application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml index 941bd7d278..7159a0256d 100644 --- a/application/src/main/resources/logback.xml +++ b/application/src/main/resources/logback.xml @@ -1,7 +1,7 @@ + + + + + + + diff --git a/common/actor/pom.xml b/common/actor/pom.xml index baab985d07..2bc3335ded 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msa/black-box-tests/src/test/resources/smokesRuleChain.xml b/msa/black-box-tests/src/test/resources/smokesRuleChain.xml index 5834ad6558..dbb22782ca 100644 --- a/msa/black-box-tests/src/test/resources/smokesRuleChain.xml +++ b/msa/black-box-tests/src/test/resources/smokesRuleChain.xml @@ -1,7 +1,7 @@ tb + web-ui vc-executor vc-executor-docker - js-executor - web-ui tb-node transport + js-executor diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index cbfbebf86c..68e428992a 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -1,5 +1,5 @@ # -# Copyright © 2016-2022 The Thingsboard Authors +# Copyright © 2016-2023 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/msa/tb-node/docker/start-tb-node.sh b/msa/tb-node/docker/start-tb-node.sh index 3c5f43bfff..c979f72dad 100755 --- a/msa/tb-node/docker/start-tb-node.sh +++ b/msa/tb-node/docker/start-tb-node.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright © 2016-2022 The Thingsboard Authors +# Copyright © 2016-2023 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index bb53c84a2f..8779dd6dd1 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -1,6 +1,6 @@ 4.1.0 - 4.3.1.0 2.7.2 1.5.2 5.8.2 @@ -146,8 +147,10 @@ 1.0.0 4.6.0 5.2.0 - 2.19.0 - 2.8 + 2.21.0 + 2.12.0 + 1.12.1 + 3.4.0 @@ -658,7 +661,7 @@ ${surefire.version} - --illegal-access=permit + --illegal-access=permit -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=20 @@ -817,13 +820,16 @@ docker/haproxy/** docker/tb-node/** ui/** - src/.browserslistrc + **/.browserslistrc **/yarn.lock + **/.yarnrc + **/.angular/** **/*.raw **/*.patch **/apache/cassandra/io/** .run/** **/NetworkReceive.java + **/lwm2m-registry/** JAVADOC_STYLE @@ -1427,6 +1433,11 @@ classmate ${fasterxml-classmate.version} + + com.auth0 + java-jwt + ${auth0-jwt.version} + com.github.fge json-schema-validator @@ -1600,26 +1611,6 @@ - - org.cassandraunit - cassandra-unit - ${cassandra-unit.version} - test - - - junit - junit - - - org.hamcrest - hamcrest-core - - - org.hamcrest - hamcrest-library - - - org.apache.cassandra cassandra-all @@ -1713,6 +1704,11 @@ org.eclipse.paho.client.mqttv3 ${paho.client.version} + + org.eclipse.paho + org.eclipse.paho.mqttv5.client + ${paho.mqttv5.client.version} + org.apache.curator curator-x-discovery @@ -1738,6 +1734,12 @@ bcpkix-jdk15on ${bouncycastle.version} + + org.testcontainers + cassandra + ${testcontainers.version} + test + org.testcontainers postgresql @@ -1973,6 +1975,11 @@ aerogear-otp-java ${aerogear-otp.version} + + com.slack.api + slack-api-client + ${slack-api.version} + org.eclipse.jgit org.eclipse.jgit @@ -1988,6 +1995,11 @@ exp4j ${exp4j.version} + + com.github.dblock + oshi-core + ${oshi.version} + diff --git a/pull_request_template.md b/pull_request_template.md index 21362429dc..4fd83ec4f0 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -12,7 +12,7 @@ Put your PR description here instead of this sentence. - [ ] Description contains brief notes about what needs to be added to the documentation. - [ ] No merge conflicts, commented blocks of code, code formatting issues. - [ ] Changes are backward compatible or upgrade script is provided. -- [ ] Similar PR is opened for PE version to simplify merge. Required for internal contributors only. +- [ ] Similar PR is opened for PE version to simplify merge. Crosslinks between PRs added. Required for internal contributors only. ## Front-End feature checklist diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 943466a411..1bf93e4dc4 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -1,6 +1,6 @@ + + + search + + + account_circle + alarm.unassigned + + + + +

+ + +
+ + + + {{ translate.get('user.no-users-matching', {entity: searchText}) | async }} + + + + + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss new file mode 100644 index 0000000000..5472d60495 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + width: 100%; + overflow: auto; + background: white; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15); + border-radius: 4px; +} + +::ng-deep { + mat-form-field.search-users { + padding: 8px; + height: 340px; + font-size: 14px; + background-color: white; + } + + .mat-form-field-appearance-outline .mdc-notched-outline__trailing{ + color: rgba(0, 0, 0, 0.12) !important; + } + + .tb-assignee-autocomplete { + &.tb-assignee-autocomplete.mat-mdc-autocomplete-panel { + position: relative; + margin-top: 8px; + box-shadow: none !important; + } + .mat-mdc-option { + font-size: 14px; + border: none; + height: 52px !important; + .unassigned-icon { + color: rgba(0, 0, 0, 0.38); + font-size: 28px; + width: 28px; + height: 28px; + margin-right: 8px; + } + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 8px; + border-radius: 50%; + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + color: white; + font-size: 13px; + font-weight: 700 + } + .user-display-name { + max-width: 80%; + overflow: hidden; + span { + overflow: hidden; + text-overflow: ellipsis; + } + span + span { + color: rgba(0, 0, 0, 0.38); + } + } + .mdc-list-item__primary-text { + width: 100%; + display: flex; + align-items: center; + line-height: normal; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts new file mode 100644 index 0000000000..ce448d0321 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -0,0 +1,235 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ElementRef, + Inject, + InjectionToken, OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Observable, of, Subject } from 'rxjs'; +import { + catchError, + debounceTime, + distinctUntilChanged, + map, + share, + switchMap, + takeUntil, +} from 'rxjs/operators'; +import { User, UserEmailInfo } from '@shared/models/user.model'; +import { TranslateService } from '@ngx-translate/core'; +import { UserService } from '@core/http/user.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction } from '@shared/models/page/sort-order'; +import { emptyPageData } from '@shared/models/page/page-data'; +import { AlarmService } from '@core/http/alarm.service'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { UtilsService } from '@core/services/utils.service'; + +export const ALARM_ASSIGNEE_PANEL_DATA = new InjectionToken('AlarmAssigneePanelData'); + +export interface AlarmAssigneePanelData { + alarmId: string; + assigneeId: string; +} + +@Component({ + selector: 'tb-alarm-assignee-panel', + templateUrl: './alarm-assignee-panel.component.html', + styleUrls: ['./alarm-assignee-panel.component.scss'] +}) +export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDestroy { + + private dirty = false; + + alarmId: string; + + assigneeId?: string; + + reassigned: boolean = false; + + selectUserFormGroup: FormGroup; + + @ViewChild('userInput', {static: true}) userInput: ElementRef; + + filteredUsers: Observable>; + + searchText = ''; + + private destroy$ = new Subject(); + + constructor(@Inject(ALARM_ASSIGNEE_PANEL_DATA) public data: AlarmAssigneePanelData, + public overlayRef: OverlayRef, + public translate: TranslateService, + private userService: UserService, + private alarmService: AlarmService, + private fb: FormBuilder, + private utilsService: UtilsService) { + this.alarmId = data.alarmId; + this.assigneeId = data.assigneeId; + this.selectUserFormGroup = this.fb.group({ + user: [null] + }); + } + + ngOnInit() { + this.filteredUsers = this.selectUserFormGroup.get('user').valueChanges + .pipe( + debounceTime(150), + map(value => { + return value ? (typeof value === 'string' ? value : '') : '' + }), + distinctUntilChanged(), + switchMap(name => this.fetchUsers(name)), + share(), + takeUntil(this.destroy$) + ); + } + + ngAfterViewInit() { + setTimeout(() => { + this.userInput.nativeElement.focus(); + }, 0) + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + displayUserFn(user?: User): string | undefined { + return user ? user.email : undefined; + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.clear(); + const user: User = event.option.value; + if (user) { + this.assign(user); + } else { + this.unassign(); + } + } + + assign(user: User): void { + this.alarmService.assignAlarm(this.alarmId, user.id.id, {ignoreLoading: true}).subscribe( + () => { + this.reassigned = true; + this.overlayRef.dispose() + }); + } + + unassign(): void { + this.alarmService.unassignAlarm(this.alarmId, {ignoreLoading: true}).subscribe( + () => { + this.reassigned = true; + this.overlayRef.dispose() + }); + } + + fetchUsers(searchText?: string): Observable> { + this.searchText = searchText; + const pageLink = new PageLink(50, 0, searchText, { + property: 'email', + direction: Direction.ASC + }); + return this.userService.getUsersForAssign(this.alarmId, pageLink, {ignoreLoading: true}) + .pipe( + catchError(() => of(emptyPageData())), + map(pageData => { + return pageData.data; + }) + ); + } + + onFocus(): void { + if (!this.dirty) { + this.selectUserFormGroup.get('user').updateValueAndValidity({onlySelf: true}); + this.dirty = true; + } + } + + clear() { + this.selectUserFormGroup.get('user').patchValue('', {emitEvent: true}); + setTimeout(() => { + this.userInput.nativeElement.blur(); + this.userInput.nativeElement.focus(); + }, 0); + } + + getUserDisplayName(entity: User) { + let displayName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + displayName += entity.firstName; + } + if (entity.lastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.lastName; + } + } else { + displayName = entity.email; + } + return displayName; + } + + getUserInitials(entity: User): string { + let initials = ''; + if (entity.firstName && entity.firstName.length || + entity.lastName && entity.lastName.length) { + if (entity.firstName) { + initials += entity.firstName.charAt(0); + } + if (entity.lastName) { + initials += entity.lastName.charAt(0); + } + } else { + initials += entity.email.charAt(0); + } + return initials.toUpperCase(); + } + + getFullName(entity: User): string { + let fullName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + fullName += entity.firstName; + } + if (entity.lastName) { + if (fullName.length > 0) { + fullName += ' '; + } + fullName += entity.lastName; + } + } + return fullName; + } + + getAvatarBgColor(entity: User) { + return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html new file mode 100644 index 0000000000..dd4c8ee1d9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html @@ -0,0 +1,30 @@ + + + + alarm.assignee + + + {{ getUserInitials(alarm.assignee) }} + + account_circle + arrow_drop_down + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss new file mode 100644 index 0000000000..82f10650cf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + color: white; + font-size: 13px; + font-weight: 700; + margin-left: 12px; + margin-right: 20px; +} + +.unassigned-icon { + width: 28px; + height: 28px; + font-size: 28px; + color: rgba(0, 0, 0, 0.38); + overflow: visible; + margin-left: 12px; + margin-right: 20px; + padding: 0; +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts new file mode 100644 index 0000000000..1fb1d3f689 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts @@ -0,0 +1,157 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Injector, Input, Output, StaticProvider, ViewContainerRef } from '@angular/core'; +import { UtilsService } from '@core/services/utils.service'; +import { AlarmAssignee, AlarmInfo } from '@shared/models/alarm.models'; +import { + ALARM_ASSIGNEE_PANEL_DATA, + AlarmAssigneePanelComponent, + AlarmAssigneePanelData +} from '@home/components/alarm/alarm-assignee-panel.component'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-alarm-assignee', + templateUrl: './alarm-assignee.component.html', + styleUrls: ['./alarm-assignee.component.scss'] +}) +export class AlarmAssigneeComponent { + @Input() + alarm: AlarmInfo; + + @Input() + allowAssign: boolean; + + @Output() + alarmReassigned = new EventEmitter(); + + constructor(private utilsService: UtilsService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private translateService: TranslateService) { + } + + getAssignee() { + if (this.alarm) { + if (this.alarm.assignee) { + return this.getUserDisplayName(this.alarm.assignee); + } else { + return this.translateService.instant('alarm.unassigned'); + } + } + } + + getUserDisplayName(entity: AlarmAssignee) { + let displayName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + displayName += entity.firstName; + } + if (entity.lastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.lastName; + } + } else { + displayName = entity.email; + } + return displayName; + } + + getUserInitials(entity: AlarmAssignee): string { + let initials = ''; + if (entity.firstName && entity.firstName.length || + entity.lastName && entity.lastName.length) { + if (entity.firstName) { + initials += entity.firstName.charAt(0); + } + if (entity.lastName) { + initials += entity.lastName.charAt(0); + } + } else { + initials += entity.email.charAt(0); + } + return initials.toUpperCase(); + } + + getFullName(entity: AlarmAssignee): string { + let fullName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + fullName += entity.firstName; + } + if (entity.lastName) { + if (fullName.length > 0) { + fullName += ' '; + } + fullName += entity.lastName; + } + } + return fullName; + } + + getAvatarBgColor(entity: AlarmAssignee) { + return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + + openAlarmAssigneePanel($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + if (this.allowAssign) { + const target = $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'center', + originY: 'bottom', + overlayX: 'center', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.width = (target as HTMLElement).offsetWidth; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: ALARM_ASSIGNEE_PANEL_DATA, + useValue: { + alarmId: alarm.id.id, + assigneeId: alarm.assigneeId?.id + } as AlarmAssigneePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(AlarmAssigneePanelComponent, + this.viewContainerRef, injector)).onDestroy(() => this.alarmReassigned.emit(true)); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html new file mode 100644 index 0000000000..d7e3366a35 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html @@ -0,0 +1,33 @@ + +
+ + + + + + +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts new file mode 100644 index 0000000000..0aa568bdd0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Router } from '@angular/router'; +import { AlarmInfo } from '@shared/models/alarm.models'; + +export interface AlarmCommentDialogData { + alarmId?: string; + alarm?: AlarmInfo; +} + +@Component({ + selector: 'tb-alarm-comment-dialog', + templateUrl: './alarm-comment-dialog.component.html', + styleUrls: [] +}) +export class AlarmCommentDialogComponent extends DialogComponent { + + alarmId: string; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AlarmCommentDialogData, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + this.alarmId = this.data.alarmId; + } + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html new file mode 100644 index 0000000000..ac2bb73d05 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html @@ -0,0 +1,181 @@ + + +
+
+
+ + alarm-activity.activity + +
+ + + +
+
+
+
+ + + +
+
+
+ + {{ displayDataElement.commentText }} + + + {{ displayDataElement.createdDateAgo }} + +
+ +
+
+ {{ getUserInitials(displayDataElement.displayName) }} +
+
+
+ {{ displayDataElement.displayName }} + + {{ displayDataElement.createdDateAgo }} + + + Edited + +
+ {{ displayDataElement.commentText }} +
+
+ + +
+
+ +
+
+ {{ getUserInitials(displayDataElement.displayName) }} +
+ + +
+ + +
+
+
+
+
+
+
+ + + +
+ +
+
+
+ {{ getUserInitials(userDisplayName) }} +
+ + + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss new file mode 100644 index 0000000000..c7b0d65353 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss @@ -0,0 +1,220 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@import '../theme.scss'; + +$primary-color: rgba(mat.get-color-from-palette($tb-primary, 50), 0.4); +$border: 1px solid mat.get-color-from-palette($tb-primary); + +:host { + .tb-alarm-comments { + background-color: $primary-color; + max-width: 600px; + width: 100%; + + .comment-input { + position: sticky; + z-index: 1; + + .inner-wrap { + border: $border; + padding: 0 24px; + background-color: $primary-color; + } + + &.oldest-first { + bottom: -24px; + box-shadow: 0px -4px 12px rgba(0, 0, 0, 0.04); + + .inner-wrap { + border-radius: 0 0 8px 8px; + } + + &.activity-only { + bottom: -1px; + + .inner-wrap { + border: none + } + } + } + + &.newest-first { + top: 24px; + box-shadow: 0 8px 10px rgba(23, 33, 90, 0.08); + + .inner-wrap { + border-top: none; + } + + &.activity-only { + top: 48px; + box-shadow: 0 8px 10px rgba(23, 33, 90, 0.08); + + .inner-wrap { + border: none; + } + } + } + } + + .header { + z-index: 1; + position: sticky; + top: -24px; + background-color: white; + + &.activity-only { + top: 0; + } + + &-container { + padding: 0 24px; + background-color: $primary-color; + border: $border; + border-bottom: none; + border-radius: 8px 8px 0px 0px; + + &.activity-only { + border: none; + } + + &.asc { + border-bottom: $border; + box-shadow: 0px 4px 10px rgba(23, 33, 90, 0.08); + + &.activity-only { + border: none; + } + } + + .header-title { + color: rgba(0, 0, 0, 0.76); + letter-spacing: 0.25px; + font-weight: 500; + } + + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + } + } + + .comments-container { + border: $border; + border-top: none; + border-radius: 0px 0px 8px 8px; + + &.activity-only { + border: none; + } + + &.asc { + border-bottom: none; + border-radius: unset; + } + } + + .user-comment { + padding: 4px; + border-radius: 8px; + &:hover { + background-color: $primary-color; + } + } + + .user-avatar { + width: 28px; + min-width: 28px; + height: 28px; + min-height: 28px; + border-radius: 50%; + font-weight: 700; + color: white; + font-size: 13px; + } + + .user-name { + font-size: 16px; + color: rgba(0, 0, 0, 0.76); + font-weight: 500; + letter-spacing: 0.25px; + } + + .time { + font-size: 14px; + font-weight: 400; + color: rgba(0, 0, 0, 0.38); + letter-spacing: 0.2px; + border-radius: 16px; + padding: 0 3px; + &:hover { + background: rgba(0, 0, 0, 0.04); + } + } + + .system-comment { + margin-left: 38px; + @media #{$mat-xs} { + margin-left: 0; + } + } + + .system-text { + font-weight: 500; + font-size: 14px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.38); + } + + .text { + white-space: pre-line; + word-break: break-word; + font-size: 16px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.54); + } + + .action-buttons { + visibility: hidden; + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + } + + .show-buttons { + visibility: visible; + } + + .green-button { + color: #00695C; + } + + .red-button { + color: #D12730; + } + + .mat-form-field { + font-size: 16px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.76); + } + + textarea { + letter-spacing: 0.15px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts new file mode 100644 index 0000000000..5ba3c39a81 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -0,0 +1,325 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input, OnInit } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { AlarmCommentService } from '@core/http/alarm-comment.service'; +import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; +import { DialogService } from '@core/services/dialog.service'; +import { AuthUser, User } from '@shared/models/user.model'; +import { getCurrentAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link'; +import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; +import { map } from 'rxjs/operators'; +import { AlarmComment, AlarmCommentInfo, AlarmCommentType } from '@shared/models/alarm.models'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DatePipe } from '@angular/common'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; + +interface AlarmCommentsDisplayData { + commentId?: string, + displayName?: string, + createdTime: string, + createdDateAgo?: string, + edit?: boolean, + isEdited?: boolean, + editedTime?: string; + editedDateAgo?: string, + showActions?: boolean, + commentText?: string, + isSystemComment?: boolean, + avatarBgColor?: string +} + +@Component({ + selector: 'tb-alarm-comment', + templateUrl: './alarm-comment.component.html', + styleUrls: ['./alarm-comment.component.scss'] +}) +export class AlarmCommentComponent implements OnInit { + @Input() + alarmId: string; + + @Input() + alarmActivityOnly: boolean = false; + + authUser: AuthUser; + + alarmCommentFormGroup: FormGroup; + + alarmComments: Array; + + displayData: Array = new Array(); + + alarmCommentSortOrder: SortOrder = { + property: 'createdTime', + direction: Direction.DESC + }; + + editMode: boolean = false; + + userDisplayName$ = this.store.pipe( + select(selectUserDetails), + map((user) => this.getUserDisplayName(user)) + ); + + currentUserDisplayName: string; + currentUserAvatarColor: string; + + constructor(protected store: Store, + private translate: TranslateService, + private alarmCommentService: AlarmCommentService, + public fb: FormBuilder, + private dialogService: DialogService, + public dateAgoPipe: DateAgoPipe, + private utilsService: UtilsService, + private datePipe: DatePipe, + private importExportService: ImportExportService) { + + this.authUser = getCurrentAuthUser(store); + + this.alarmCommentFormGroup = this.fb.group( + { + alarmCommentEdit: [''], + alarmComment: [''] + } + ); + } + + ngOnInit() { + this.loadAlarmComments(); + this.currentUserAvatarColor = this.utilsService.stringToHslColor(this.currentUserDisplayName, + 60, 40); + } + + loadAlarmComments(): void { + this.alarmCommentService.getAlarmComments(this.alarmId, new PageLink(MAX_SAFE_PAGE_SIZE, 0, null, + this.alarmCommentSortOrder), {ignoreLoading: true}).subscribe( + (pagedData) => { + this.alarmComments = pagedData.data; + this.displayData.length = 0; + for (let alarmComment of pagedData.data) { + let displayDataElement = {} as AlarmCommentsDisplayData; + displayDataElement.createdTime = this.datePipe.transform(alarmComment.createdTime, 'yyyy-MM-dd HH:mm:ss'); + displayDataElement.createdDateAgo = this.dateAgoPipe.transform(alarmComment.createdTime); + displayDataElement.commentText = alarmComment.comment.text; + displayDataElement.isSystemComment = alarmComment.type === AlarmCommentType.SYSTEM; + if (alarmComment.type === AlarmCommentType.OTHER) { + displayDataElement.commentId = alarmComment.id.id; + displayDataElement.displayName = this.getUserDisplayName(alarmComment); + displayDataElement.edit = false; + displayDataElement.isEdited = alarmComment.comment.edited; + displayDataElement.editedTime = this.datePipe.transform(alarmComment.comment.editedOn, 'yyyy-MM-dd HH:mm:ss'); + displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn) + '\n'; + displayDataElement.showActions = false; + displayDataElement.isSystemComment = false; + displayDataElement.avatarBgColor = this.utilsService.stringToHslColor(displayDataElement.displayName, + 40, 60); + } + this.displayData.push(displayDataElement); + } + } + ) + } + + changeSortDirection() { + let currentDirection = this.alarmCommentSortOrder.direction; + this.alarmCommentSortOrder.direction = currentDirection === Direction.DESC ? Direction.ASC : Direction.DESC; + this.loadAlarmComments(); + } + + exportAlarmActivity() { + let fileName = this.translate.instant('alarm.alarm') + '_' + this.translate.instant('alarm-activity.activity'); + this.importExportService.exportCsv(this.getDataForExport(), fileName.toLowerCase()); + } + + saveComment(): void { + const commentInputValue: string = this.getAlarmCommentFormControl().value; + if (commentInputValue) { + const comment: AlarmComment = { + alarmId: { + id: this.alarmId, + entityType: EntityType.ALARM + }, + type: AlarmCommentType.OTHER, + comment: { + text: commentInputValue + } + } + this.doSave(comment); + this.clearCommentInput(); + } + } + + saveEditedComment(commentId: string): void { + const commentEditInputValue: string = this.getAlarmCommentEditFormControl().value; + if (commentEditInputValue) { + const editedComment: AlarmComment = this.getAlarmCommentById(commentId); + editedComment.comment.text = commentEditInputValue; + this.doSave(editedComment); + this.clearCommentEditInput(); + this.editMode = false; + this.getAlarmCommentFormControl().enable({emitEvent: false}); + } + } + + private doSave(comment: AlarmComment): void { + this.alarmCommentService.saveAlarmComment(this.alarmId, comment, {ignoreLoading: true}).subscribe( + () => { + this.loadAlarmComments(); + } + ) + } + + editComment(commentId: string): void { + const commentDisplayData = this.getDataElementByCommentId(commentId); + commentDisplayData.edit = true; + this.editMode = true; + this.getAlarmCommentEditFormControl().patchValue(commentDisplayData.commentText); + this.getAlarmCommentFormControl().disable({emitEvent: false}); + } + + cancelEdit(commentId: string): void { + const commentDisplayData = this.getDataElementByCommentId(commentId); + commentDisplayData.edit = false; + this.editMode = false; + this.getAlarmCommentFormControl().enable({emitEvent: false}); + } + + deleteComment(commentId: string): void { + const alarmCommentInfo: AlarmComment = this.getAlarmCommentById(commentId); + const commentText: string = alarmCommentInfo.comment.text; + this.dialogService.confirm( + this.translate.instant('alarm-activity.delete-alarm-comment'), + commentText, + this.translate.instant('action.cancel'), + this.translate.instant('action.delete')).subscribe( + (result) => { + if (result) { + this.alarmCommentService.deleteAlarmComments(this.alarmId, commentId, {ignoreLoading: true}) + .subscribe(() => { + this.loadAlarmComments(); + } + ) + } + } + ) + } + + getSortDirectionIcon() { + return this.alarmCommentSortOrder.direction === Direction.DESC ? 'mdi:sort-descending' : 'mdi:sort-ascending' + } + + getSortDirectionTooltipText() { + let text = this.alarmCommentSortOrder.direction === Direction.DESC ? 'alarm-activity.newest-first' : + 'alarm-activity.oldest-first'; + return this.translate.instant(text); + } + + isDirectionAscending() { + return this.alarmCommentSortOrder.direction === Direction.ASC; + } + + onCommentMouseEnter(commentId: string, displayDataIndex: number): void { + if (!this.editMode) { + const alarmUserId = this.getAlarmCommentById(commentId).userId.id; + if (this.authUser.userId === alarmUserId) { + this.displayData[displayDataIndex].showActions = true; + } + } + } + + onCommentMouseLeave(displayDataIndex: number): void { + this.displayData[displayDataIndex].showActions = false; + } + + getUserInitials(userName: string): string { + let initials = ''; + const userNameSplit = userName.split(' '); + initials += userNameSplit[0].charAt(0).toUpperCase(); + if (userNameSplit.length > 1) { + initials += userNameSplit[userNameSplit.length - 1].charAt(0).toUpperCase(); + } + return initials; + } + + getCurrentUserBgColor(userDisplayName: string) { + return this.utilsService.stringToHslColor(userDisplayName, 40, 60); + } + + private getUserDisplayName(alarmCommentInfo: AlarmCommentInfo | User): string { + let name = ''; + if ((alarmCommentInfo.firstName && alarmCommentInfo.firstName.length > 0) || + (alarmCommentInfo.lastName && alarmCommentInfo.lastName.length > 0)) { + if (alarmCommentInfo.firstName) { + name += alarmCommentInfo.firstName; + } + if (alarmCommentInfo.lastName) { + if (name.length > 0) { + name += ' '; + } + name += alarmCommentInfo.lastName; + } + } else { + name = alarmCommentInfo.email; + } + return name; + } + + getAlarmCommentFormControl(): AbstractControl { + return this.alarmCommentFormGroup.get('alarmComment'); + } + + getAlarmCommentEditFormControl(): AbstractControl { + return this.alarmCommentFormGroup.get('alarmCommentEdit'); + } + + private clearCommentInput(): void { + this.getAlarmCommentFormControl().patchValue(''); + } + + private clearCommentEditInput(): void { + this.getAlarmCommentEditFormControl().patchValue(''); + } + + private getAlarmCommentById(id: string): AlarmComment { + return this.alarmComments.find(comment => comment.id.id === id); + } + + private getDataElementByCommentId(commentId: string): AlarmCommentsDisplayData { + return this.displayData.find(commentDisplayData => commentDisplayData.commentId === commentId); + } + + private getDataForExport() { + let dataToExport = []; + for (let row of this.displayData) { + let exportRow = { + [this.translate.instant('alarm-activity.author')]: row.isSystemComment ? + this.translate.instant('alarm-activity.system') : row.displayName, + [this.translate.instant('alarm-activity.created-date')]: row.createdTime, + [this.translate.instant('alarm-activity.edited-date')]: row.editedTime, + [this.translate.instant('alarm-activity.text')]: row.commentText + } + dataToExport.push(exportRow) + } + return dataToExport; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index 78f72f55f4..85bdf96ea3 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -1,6 +1,6 @@ - + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss index 351d962496..8de701f3e3 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,43 @@ */ :host ::ng-deep { tb-entities-table { - .mat-drawer-container { - background-color: white; + &.tb-details-mode { + .mat-drawer-container { + background-color: white; + } + } + .assignee-cell { + display: flex; + justify-content: flex-start; + align-items: center; + .assigned-container { + max-width: 180px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 8px; + border-radius: 50%; + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + color: white; + font-size: 13px; + font-weight: 700; + } + } + .material-icons.unassigned-icon { + width: 28px; + height: 28px; + font-size: 28px; + margin-right: 8px; + color: rgba(0, 0, 0, 0.38); + overflow: visible; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts index d642bcef7a..655d1360d8 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatDialog } from '@angular/material/dialog'; @@ -22,10 +22,14 @@ import { EntityId } from '@shared/models/id/entity-id'; import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; import { DialogService } from '@core/services/dialog.service'; import { AlarmTableConfig } from './alarm-table-config'; -import { AlarmSearchStatus } from '@shared/models/alarm.models'; +import { AlarmSearchStatus, AlarmsMode } from '@shared/models/alarm.models'; import { AlarmService } from '@app/core/http/alarm.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { Overlay } from '@angular/cdk/overlay'; +import { UtilsService } from '@core/services/utils.service'; +import { ActivatedRoute } from '@angular/router'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-alarm-table', @@ -37,6 +41,8 @@ export class AlarmTableComponent implements OnInit { activeValue = false; dirtyValue = false; entityIdValue: EntityId; + alarmsMode = AlarmsMode.ENTITY; + detailsMode = true; @Input() set active(active: boolean) { @@ -71,20 +77,38 @@ export class AlarmTableComponent implements OnInit { private translate: TranslateService, private datePipe: DatePipe, private dialog: MatDialog, - private store: Store) { + private store: Store, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef, + private utilsService: UtilsService, + private route: ActivatedRoute) { } ngOnInit() { this.dirtyValue = !this.activeValue; + const pageMode = !!this.route.snapshot.data.isPage; + if (pageMode) { + this.detailsMode = false; + } + if (isDefinedAndNotNull(this.route.snapshot.data.alarmsMode)) { + this.alarmsMode = this.route.snapshot.data.alarmsMode; + } this.alarmTableConfig = new AlarmTableConfig( this.alarmService, this.dialogService, this.translate, this.datePipe, this.dialog, + this.alarmsMode, this.entityIdValue, AlarmSearchStatus.ANY, - this.store + this.store, + this.viewContainerRef, + this.overlay, + this.cd, + this.utilsService, + pageMode ); } diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.html index 3fea8662a5..e537a27f66 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.html @@ -1,6 +1,6 @@ - {{ alias }} + diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.ts index 7c776385a5..3880c88c3d 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-autocomplete.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ /// import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { catchError, debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; @@ -40,7 +40,7 @@ import { isDefinedAndNotNull } from '@core/utils'; }) export class AliasesEntityAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit { - selectEntityInfoFormGroup: FormGroup; + selectEntityInfoFormGroup: UntypedFormGroup; modelValue: EntityInfo | null; @@ -73,7 +73,7 @@ export class AliasesEntityAutocompleteComponent implements ControlValueAccessor, constructor(private store: Store, public translate: TranslateService, private entityService: EntityService, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { this.selectEntityInfoFormGroup = this.fb.group({ entityInfo: [null] }); diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html index bea2e9a9bd..a0986154fc 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html @@ -1,6 +1,6 @@ - +

{{ (isAdd ? 'alias.add' : 'alias.edit') | translate }}

diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.scss b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.scss index 5a0d6f16fc..45920f4f75 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts index cb3d24ba79..6cc8b0e59b 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { - FormBuilder, - FormControl, - FormGroup, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, FormGroupDirective, NgForm, ValidatorFn, @@ -59,7 +59,7 @@ export class EntityAliasDialogComponent extends DialogComponent, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private utils: UtilsService, public translate: TranslateService, private entityService: EntityService) { @@ -103,7 +103,7 @@ export class EntityAliasDialogComponent extends DialogComponent { + return (c: UntypedFormControl) => { const newAlias = c.value.trim(); const found = this.entityAliases.find((entityAlias) => entityAlias.alias === newAlias); if (found) { @@ -122,7 +122,7 @@ export class EntityAliasDialogComponent extends DialogComponent - - - + {{ 'entity.entity-alias' | translate }} + - +

{{ title | translate }}

@@ -32,7 +32,7 @@
- alias.name + alias.name alias.entity-filter alias.resolve-multiple @@ -45,8 +45,7 @@ *ngFor="let entityAliasControl of entityAliasesFormArray().controls; let $index = index"> {{$index + 1}}.
- - + {{ 'entity.alias-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.scss b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.scss index a4314e5efb..c89bd33373 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ } :host ::ng-deep { - .mat-dialog-content { + .mat-mdc-dialog-content { padding-top: 0 !important; padding-bottom: 0 !important; } diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts index 419649eb84..d15fc17df5 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -21,10 +21,10 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AbstractControl, - FormArray, - FormBuilder, - FormControl, - FormGroup, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, FormGroupDirective, NgForm, Validators @@ -67,7 +67,7 @@ export class EntityAliasesDialogComponent extends DialogComponent} = {}; - entityAliasesFormGroup: FormGroup; + entityAliasesFormGroup: UntypedFormGroup; submitted = false; @@ -76,7 +76,7 @@ export class EntityAliasesDialogComponent extends DialogComponent, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private utils: UtilsService, private translate: TranslateService, private dialogs: DialogService, @@ -157,14 +157,14 @@ export class EntityAliasesDialogComponent extends DialogComponent { if (entityAlias) { if (isAdd) { - (this.entityAliasesFormGroup.get('entityAliases') as FormArray) + (this.entityAliasesFormGroup.get('entityAliases') as UntypedFormArray) .push(this.createEntityAliasFormControl(entityAlias.id, entityAlias)); } else { - const aliasFormControl = (this.entityAliasesFormGroup.get('entityAliases') as FormArray).at(index); + const aliasFormControl = (this.entityAliasesFormGroup.get('entityAliases') as UntypedFormArray).at(index); aliasFormControl.get('alias').patchValue(entityAlias.alias); aliasFormControl.get('filter').patchValue(entityAlias.filter); aliasFormControl.get('resolveMultiple').patchValue(entityAlias.filter.resolveMultiple); diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html index b68bf2fa33..c48299986e 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html @@ -1,6 +1,6 @@
-
- +
+
{{telemetryTypeTranslationsMap.get(attributeScope) | translate}} - + attribute.attributes-scope - @@ -58,7 +58,7 @@
- +
- +
{{ (attributeScope === latestTelemetryTypes.LATEST_TELEMETRY ? @@ -104,7 +104,7 @@
- +
{{ 'widgets-bundle.current' | translate }} @@ -170,7 +170,7 @@ class="tb-value-cell" (click)="editAttribute($event, attribute)">
- {{attribute.value | tbJson}} + {{attribute.value | tbJson}} edit @@ -249,10 +249,10 @@ widgetBundleSet" fxFlex fxLayoutAlign="center center" style="display: flex;" - class="mat-headline">widgets-bundle.empty + class="mat-headline-5">widgets-bundle.empty widget.select-widgets-bundle + class="mat-headline-5">widget.select-widgets-bundle
diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss index 10e1a87ce6..67bf37453a 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,13 @@ width: 100%; height: 100%; background: #fff; + overflow: hidden; + + &.tb-outlined-border { + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + } .mat-toolbar-tools{ min-height: auto; @@ -42,6 +49,9 @@ .table-container { overflow: auto; + .mat-mdc-table { + table-layout: fixed; + } } .tb-entity-table-info{ @@ -75,26 +85,6 @@ .mat-sort-header-sorted .mat-sort-header-arrow { opacity: 1 !important; } - mat-form-field.tb-attribute-scope { - font-size: 16px; - width: 200px; - - .mat-form-field-wrapper { - padding-bottom: 0; - } - - .mat-form-field-underline { - bottom: 0; - } - - @media #{$mat-xs} { - width: 100%; - - .mat-form-field-infix { - width: auto !important; - } - } - } mat-cell.tb-value-cell { cursor: pointer; mat-icon { diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index 9d6812e0f9..e9d67a3fdd 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html index 6004d37362..9cefd62825 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html @@ -1,6 +1,6 @@ + [formGroup]="attributeFormGroup" (ngSubmit)="update()" style="padding: 5px;">

audit-log.audit-log-details

-
{{ headerTitle }}
{{ headerSubtitle }}
@@ -46,7 +52,7 @@
- + diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.scss b/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.scss index 27b3d4f2f8..d4ef594449 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.scss +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,6 @@ .tb-header-button { .tb-btn-header { position: relative !important; - display: inline-block !important; animation: tbMoveFromTopFade .3s ease both; &.tb-hide { @@ -92,25 +91,25 @@ :host ::ng-deep { .tb-help { - .mat-icon-button.mat-primary { + .mat-mdc-icon-button.mat-primary { color: rgba(0, 0, 0, 0.52); } } - .mat-card-content { + .mat-mdc-card-content { position: relative; overflow: hidden; - > .mat-tab-group { - > .mat-tab-body-wrapper { + > .mat-mdc-tab-group { + > .mat-mdc-tab-body-wrapper { position: absolute; top: 49px; left: 0; right: 0; bottom: 0; } - > .mat-tab-header { - .mat-tab-label { + > .mat-mdc-tab-header { + .mat-mdc-tab { min-width: 40px; } } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts index d0b141256e..68dc59407e 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import { AppState } from '@core/core.state'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { BaseData, HasId } from '@shared/models/base-data'; import { ActivatedRoute, Router } from '@angular/router'; -import { FormGroup } from '@angular/forms'; +import { UntypedFormGroup } from '@angular/forms'; import { AssetId } from '@shared/models/id/asset-id'; import { TranslateService } from '@ngx-translate/core'; import { deepClone } from '@core/utils'; @@ -140,10 +140,14 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl }); } - confirmForm(): FormGroup { + confirmForm(): UntypedFormGroup { return this.detailsForm; } + goBack(): void { + this.router.navigate(['../'], { relativeTo: this.route }); + } + private onUpdateEntity() { this.broadcast.broadcast('updateBreadcrumb'); this.isReadOnly = this.entitiesTableConfig.detailsReadonly(this.entity); @@ -164,7 +168,7 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl if (result) { this.entitiesTableConfig.deleteEntity(entity.id).subscribe( () => { - this.router.navigate(['../'], {relativeTo: this.route}); + this.goBack(); } ); } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html index cba0b85ce9..d4977a3ec5 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html @@ -1,6 +1,6 @@
- - + {{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}} diff --git a/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts index 30807d698e..1f6a50a031 100644 --- a/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, - FormBuilder, - FormGroup, + UntypedFormBuilder, + UntypedFormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, @@ -60,7 +60,7 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, Va valueTypeEnum = EntityKeyValueType; - booleanFilterPredicateFormGroup: FormGroup; + booleanFilterPredicateFormGroup: UntypedFormGroup; booleanOperations = Object.keys(BooleanOperation); booleanOperationEnum = BooleanOperation; @@ -68,7 +68,7 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, Va private propagateChange = null; - constructor(private fb: FormBuilder) { + constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html index e9b48f0d26..0a4bd2d3cd 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html @@ -1,6 +1,6 @@ - +

filter.complex-filter

diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts index a91d9cddc5..6eca715187 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { @@ -40,7 +40,7 @@ export class ComplexFilterPredicateDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { - complexFilterFormGroup: FormGroup; + complexFilterFormGroup: UntypedFormGroup; complexOperations = Object.keys(ComplexOperation); complexOperationEnum = ComplexOperation; @@ -55,7 +55,7 @@ export class ComplexFilterPredicateDialogComponent extends @Inject(MAT_DIALOG_DATA) public data: ComplexFilterPredicateDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { super(store, router, dialogRef); this.isAdd = this.data.isAdd; @@ -74,7 +74,7 @@ export class ComplexFilterPredicateDialogComponent extends ngOnInit(): void { } - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); const customErrorState = !!(control && control.invalid && this.submitted); return originalErrorState || customErrorState; diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html index 13ca2d8ddd..1caf878b88 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html @@ -1,6 +1,6 @@ - - - + {{ 'filter.filter' | translate }} + FilterSelectComponent), @@ -54,7 +54,7 @@ import { Filter } from '@shared/models/query/query.models'; }) export class FilterSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, ErrorStateMatcher { - selectFilterFormGroup: FormGroup; + selectFilterFormGroup: UntypedFormGroup; modelValue: string | null; @@ -101,7 +101,7 @@ export class FilterSelectComponent implements ControlValueAccessor, OnInit, Afte @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public translate: TranslateService, public truncate: TruncatePipe, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { this.selectFilterFormGroup = this.fb.group({ filter: [null] }); @@ -140,7 +140,7 @@ export class FilterSelectComponent implements ControlValueAccessor, OnInit, Afte ); } - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); const customErrorState = this.tbRequired && !this.modelValue; return originalErrorState || customErrorState; diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html index a4f26d10e9..54b69f1de8 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html @@ -1,6 +1,6 @@ - +

{{(data.isAdd ? 'filter.add-key-filter' : (data.readonly ? 'filter.key-filter' : 'filter.edit-key-filter')) | translate}}

@@ -46,7 +46,7 @@ [matAutocompleteDisabled]="!showAutocomplete"> @@ -60,7 +60,7 @@
- + filter.value-type.value-type diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.scss b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.scss index bfc62d5570..aeb088d38d 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,13 @@ * limitations under the License. */ :host ::ng-deep { - .entity-key { - mat-form-field { - .mat-form-field-wrapper { - .mat-form-field-infix { - width: auto; + .mat-mdc-form-field.tb-value-type { + mat-select-trigger { + .mat-icon { + vertical-align: middle; + margin-right: 8px; + svg { + vertical-align: initial; } } } diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts index b410d8a529..d8b1a21f6a 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { @@ -63,9 +63,9 @@ export class KeyFilterDialogComponent extends private dirty = false; private entityKeysName: Observable>; - private destroy$ = new Subject(); + private destroy$ = new Subject(); - keyFilterFormGroup: FormGroup; + keyFilterFormGroup: UntypedFormGroup; entityKeyTypes = this.data.telemetryKeysOnly ? @@ -96,7 +96,7 @@ export class KeyFilterDialogComponent extends private deviceProfileService: DeviceProfileService, private dialogs: DialogService, private translate: TranslateService, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { super(store, router, dialogRef); this.keyFilterFormGroup = this.fb.group( @@ -191,7 +191,7 @@ export class KeyFilterDialogComponent extends this.destroy$.complete(); } - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); const customErrorState = !!(control && control.invalid && this.submitted); return originalErrorState || customErrorState; diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html index 62a6325e4c..c29ae4ca72 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html @@ -1,6 +1,6 @@
- - + {{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}} diff --git a/ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts index f83ac2c8cb..54b0ce76ea 100644 --- a/ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, - FormBuilder, - FormGroup, + UntypedFormBuilder, + UntypedFormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, @@ -60,7 +60,7 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, Va @Input() valueType: EntityKeyValueType; - numericFilterPredicateFormGroup: FormGroup; + numericFilterPredicateFormGroup: UntypedFormGroup; valueTypeEnum = EntityKeyValueType; @@ -70,7 +70,7 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, Va private propagateChange = null; - constructor(private fb: FormBuilder) { + constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html index a121cd282e..26db51f8b0 100644 --- a/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html @@ -1,6 +1,6 @@
- - + {{stringOperationTranslations.get(stringOperationEnum[operation]) | translate}} diff --git a/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts index af5979706d..db233d526f 100644 --- a/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, - FormBuilder, - FormGroup, + UntypedFormBuilder, + UntypedFormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, @@ -60,7 +60,7 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, Val valueTypeEnum = EntityKeyValueType; - stringFilterPredicateFormGroup: FormGroup; + stringFilterPredicateFormGroup: UntypedFormGroup; stringOperations = Object.keys(StringOperation); stringOperationEnum = StringOperation; @@ -68,7 +68,7 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, Val private propagateChange = null; - constructor(private fb: FormBuilder) { + constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html index 960bcd5f9a..e5f1449eb7 100644 --- a/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html @@ -1,6 +1,6 @@ + diff --git a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts new file mode 100644 index 0000000000..6c35641348 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts @@ -0,0 +1,111 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + NgZone, + OnDestroy, + Renderer2, + ViewContainerRef +} from '@angular/core'; +import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; +import { BehaviorSubject, ReplaySubject, Subscription } from 'rxjs'; +import { distinctUntilChanged, share, tap } from 'rxjs/operators'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { ShowNotificationPopoverComponent } from '@home/components/notification/show-notification-popover.component'; +import { NotificationSubscriber } from '@shared/models/websocket/notification-ws.models'; +import { select, Store } from '@ngrx/store'; +import { selectIsAuthenticated } from '@core/auth/auth.selectors'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-notification-bell', + templateUrl: './notification-bell.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NotificationBellComponent implements OnDestroy { + + private notificationSubscriber: NotificationSubscriber; + private notificationCountSubscriber: Subscription; + private countSubject = new BehaviorSubject(0); + + count$ = this.countSubject.asObservable().pipe( + distinctUntilChanged(), + tap(() => setTimeout(() => this.cd.markForCheck())), + share({ + connector: () => new ReplaySubject(1) + }) + ); + + constructor( + private notificationWsService: NotificationWebsocketService, + private zone: NgZone, + private cd: ChangeDetectorRef, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private store: Store,) { + this.store.pipe(select(selectIsAuthenticated)).subscribe((value) => { + if (value) { + this.initSubscription(); + } + }); + } + + ngOnDestroy() { + this.unsubscribeSubscription(); + } + + showNotification($event: Event, createVersionButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + this.unsubscribeSubscription(); + const trigger = createVersionButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const showNotificationPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ShowNotificationPopoverComponent, 'bottom', true, null, + { + onClose: () => { + showNotificationPopover.hide(); + this.initSubscription(); + }, + counter: this.countSubject + }, + {maxHeight: '90vh', height: '100%', padding: '10px'}, + {width: '400px', minWidth: '100%', maxWidth: '100%'}, + {height: '100%', flexDirection: 'column', boxSizing: 'border-box', display: 'flex', margin: '0 -16px'}, false); + showNotificationPopover.tbComponentRef.instance.popoverComponent = showNotificationPopover; + } + } + + private initSubscription() { + this.notificationSubscriber = NotificationSubscriber.createNotificationCountSubscription(this.notificationWsService, this.zone); + this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.subscribe(value => this.countSubject.next(value)); + + this.notificationSubscriber.subscribe(); + } + + private unsubscribeSubscription() { + this.notificationCountSubscriber.unsubscribe(); + this.notificationSubscriber.unsubscribe(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.html b/ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.html new file mode 100644 index 0000000000..60f069c28e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.html @@ -0,0 +1,26 @@ + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.ts b/ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.ts new file mode 100644 index 0000000000..2842e220f9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/notification/send-notification-button.component.ts @@ -0,0 +1,73 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { + RequestNotificationDialogData, + SentNotificationDialogComponent +} from '@home/pages/notification/sent/sent-notification-dialog.componet'; +import { NotificationTemplate } from '@shared/models/notification.models'; +import { MatDialog } from '@angular/material/dialog'; +import { ActiveComponentService } from '@core/services/active-component.service'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { EntityType } from '@shared/models/entity-type.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; + +@Component({ + selector: 'tb-send-notification-button', + templateUrl: './send-notification-button.component.html', +}) +export class SendNotificationButtonComponent { + + authUser = getCurrentAuthUser(this.store); + + constructor(private dialog: MatDialog, + private store: Store, + private activeComponentService: ActiveComponentService) { + } + + sendNotification($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(SentNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd: true + } + }).afterClosed().subscribe((res) => { + if (res) { + const comp = this.activeComponentService.getCurrentActiveComponent(); + if (comp instanceof EntitiesTableComponent) { + const entitiesTableComponent = comp as EntitiesTableComponent; + if (entitiesTableComponent.entitiesTableConfig.entityType === EntityType.NOTIFICATION_REQUEST) { + entitiesTableComponent.entitiesTableConfig.updateData(); + } + } + } + }); + } + + public show(): boolean { + return this.authUser.authority !== Authority.CUSTOMER_USER; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.html b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.html new file mode 100644 index 0000000000..096cecd91a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.html @@ -0,0 +1,47 @@ + +
+
notification.notification
+ +
+ +
+
+
+ + + +
+
+
+ +
+ +
+ + empty notification + notification.no-notifications-yet + diff --git a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts new file mode 100644 index 0000000000..5e94289055 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectorRef, Component, Input, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Notification, NotificationRequest } from '@shared/models/notification.models'; +import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; +import { BehaviorSubject, Observable, ReplaySubject, Subscription } from 'rxjs'; +import { share, tap } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { NotificationSubscriber } from '@shared/models/websocket/notification-ws.models'; + +@Component({ + selector: 'tb-show-notification-popover', + templateUrl: './show-notification-popover.component.html', + styleUrls: [] +}) +export class ShowNotificationPopoverComponent extends PageComponent implements OnDestroy, OnInit { + + @Input() + onClose: () => void; + + @Input() + counter: BehaviorSubject; + + @Input() + popoverComponent: TbPopoverComponent; + + private notificationSubscriber: NotificationSubscriber; + private notificationCountSubscriber: Subscription; + + notifications$: Observable; + + constructor(protected store: Store, + private notificationWsService: NotificationWebsocketService, + private zone: NgZone, + private cd: ChangeDetectorRef, + private router: Router) { + super(store); + } + + ngOnInit() { + this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone, 6); + this.notifications$ = this.notificationSubscriber.notifications$.pipe( + share({ + connector: () => new ReplaySubject(1) + }), + tap(() => setTimeout(() => this.cd.markForCheck())) + ); + this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.subscribe(value => this.counter.next(value)); + this.notificationSubscriber.subscribe(); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.notificationCountSubscriber.unsubscribe(); + this.notificationSubscriber.unsubscribe(); + this.onClose(); + } + + markAsRead(id: string) { + const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]); + cmd.subscribe(); + } + + markAsAllRead($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService); + cmd.subscribe(); + } + + viewAll($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.onClose(); + this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {}); + } + + trackById(index: number, item: NotificationRequest): string { + return item.id.id; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index f15a969b19..779bc94f8f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -1,6 +1,6 @@ -
- -

device-profile.add

- -
- -
- - -
-
- - - - {{ 'device-profile.device-profile-details' | translate }} -
- - device-profile.name - - - {{ 'device-profile.name-required' | translate }} - - - {{ 'device-profile.name-max-length' | translate }} - - - - - -
{{'device-profile.mobile-dashboard-hint' | translate}}
-
- - - - device-profile.type - - - {{deviceProfileTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.type-required' | translate }} - - - - - - device-profile.description - - -
- -
- -
- {{ 'device-profile.transport-configuration' | translate }} + +

device-profile.add

+ +
+ +
+ + +
+
+ + + + {{ 'device-profile.device-profile-details' | translate }} +
- device-profile.transport-type - - - {{deviceTransportTypeTranslations.get(type) | translate}} + device-profile.name + + + {{ 'device-profile.name-required' | translate }} + + + {{ 'device-profile.name-max-length' | translate }} + + + + + + {{'device-profile.mobile-dashboard-hint' | translate}} + + + + + {{'device-profile.default-edge-rule-chain-hint' | translate}} + + + device-profile.type + + + {{deviceProfileTypeTranslations.get(type) | translate}} - - {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}} - - - {{ 'device-profile.transport-type-required' | translate }} + + {{ 'device-profile.type-required' | translate }} - - - - - -
- {{'device-profile.alarm-rules-with-count' | translate: - {count: alarmRulesFormGroup.get('alarms').value ? - alarmRulesFormGroup.get('alarms').value.length : 0} }} - - -
-
- -
- {{ 'device-profile.device-provisioning' | translate }} - - -
-
- -
-
+ + + + device-profile.description + + + + + + +
+ {{ 'device-profile.transport-configuration' | translate }} + + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}} + + + {{ 'device-profile.transport-type-required' | translate }} + + + + +
+
+ +
+ {{'device-profile.alarm-rules-with-count' | translate: + {count: alarmRulesFormGroup.get('alarms').value ? + alarmRulesFormGroup.get('alarms').value.length : 0} }} + + +
+
+ +
+ {{ 'device-profile.device-provisioning' | translate }} + + +
+
+ +
+
+
@@ -137,8 +147,8 @@ [disabled]="(isLoading$ | async)" (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}
- -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss index db53d82cb5..ca54ae2053 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,25 @@ */ @import "../../../../../scss/constants"; -:host-context(.tb-fullscreen-dialog .mat-dialog-container) { +:host { + height: 100%; + display: grid; + + .dialog-actions-row { + padding: 8px; + } +} + +:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) { @media #{$mat-lt-sm} { - .mat-dialog-content { + .mat-mdc-dialog-content { max-height: 75vh; } } } :host ::ng-deep { - .mat-dialog-content { + .mat-mdc-dialog-content { display: flex; flex-direction: column; height: 100%; @@ -32,31 +41,21 @@ .mat-stepper-horizontal { display: flex; - flex-direction: column; height: 100%; overflow: hidden; - @media #{$mat-lt-sm} { - .mat-step-label { - white-space: normal; - overflow: visible; - .mat-step-text-label { - overflow: visible; - } - } + + .mat-horizontal-stepper-wrapper { + flex: 1 1 100%; } + .mat-horizontal-content-container { - height: 530px; + height: 680px; max-height: 100%; width: 100%;; overflow-y: auto; + scrollbar-gutter: stable; @media #{$mat-gt-sm} { - min-width: 800px; - } - } - .mat-horizontal-stepper-content[aria-expanded=true] { - height: 100%; - form { - height: 100%; + min-width: 500px; } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 188fad26dc..3426593aed 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { @@ -44,12 +44,17 @@ import { } from '@shared/models/device.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { EntityType } from '@shared/models/entity-type.models'; -import { MatHorizontalStepper } from '@angular/material/stepper'; +import { MatStepper, StepperOrientation } from '@angular/material/stepper'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { deepTrim } from '@core/utils'; import { ServiceType } from '@shared/models/queue.models'; import { DashboardId } from '@shared/models/id/dashboard-id'; +import { RuleChainType } from '@shared/models/rule-chain.models'; +import { Observable } from 'rxjs'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { map } from 'rxjs/operators'; export interface AddDeviceProfileDialogData { deviceProfileName: string; @@ -65,8 +70,9 @@ export interface AddDeviceProfileDialogData { export class AddDeviceProfileDialogComponent extends DialogComponent implements AfterViewInit { - @ViewChild('addDeviceProfileStepper', {static: true}) addDeviceProfileStepper: MatHorizontalStepper; - + @ViewChild('addDeviceProfileStepper', {static: true}) addDeviceProfileStepper: MatStepper; + stepperOrientation: Observable; + stepperLabelPosition: Observable<'bottom' | 'end'>; selectedIndex = 0; showNext = true; @@ -83,26 +89,35 @@ export class AddDeviceProfileDialogComponent extends deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; - deviceProfileDetailsFormGroup: FormGroup; + deviceProfileDetailsFormGroup: UntypedFormGroup; - transportConfigFormGroup: FormGroup; + transportConfigFormGroup: UntypedFormGroup; - alarmRulesFormGroup: FormGroup; + alarmRulesFormGroup: UntypedFormGroup; - provisionConfigFormGroup: FormGroup; + provisionConfigFormGroup: UntypedFormGroup; serviceType = ServiceType.TB_RULE_ENGINE; + edgeRuleChainType = RuleChainType.EDGE; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, public dialogRef: MatDialogRef, private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector, + private breakpointObserver: BreakpointObserver, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, private deviceProfileService: DeviceProfileService, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { super(store, router, dialogRef); + this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm']) + .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); + + this.stepperLabelPosition = this.breakpointObserver.observe(MediaBreakpoints['gt-md']) + .pipe(map(({matches}) => matches ? 'end' : 'bottom')); + this.deviceProfileDetailsFormGroup = this.fb.group( { name: [data.deviceProfileName, [Validators.required, Validators.maxLength(255)]], @@ -111,6 +126,7 @@ export class AddDeviceProfileDialogComponent extends defaultRuleChainId: [null, []], defaultDashboardId: [null, []], defaultQueueName: [null, []], + defaultEdgeRuleChainId: [null, []], description: ['', []] } ); @@ -165,7 +181,7 @@ export class AddDeviceProfileDialogComponent extends } } - selectedForm(): FormGroup { + selectedForm(): UntypedFormGroup { switch (this.selectedIndex) { case 0: return this.deviceProfileDetailsFormGroup; @@ -205,6 +221,9 @@ export class AddDeviceProfileDialogComponent extends if (this.deviceProfileDetailsFormGroup.get('defaultDashboardId').value) { deviceProfile.defaultDashboardId = new DashboardId(this.deviceProfileDetailsFormGroup.get('defaultDashboardId').value); } + if (this.deviceProfileDetailsFormGroup.get('defaultEdgeRuleChainId').value) { + deviceProfile.defaultEdgeRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultEdgeRuleChainId').value); + } this.deviceProfileService.saveDeviceProfile(deepTrim(deviceProfile)).subscribe( (savedDeviceProfile) => { this.dialogRef.close(savedDeviceProfile); diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html index 552e0e4c47..d4d8644aad 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html @@ -1,6 +1,6 @@
- - +
- - + {{'filter.no-dynamic-value' | translate}} @@ -53,8 +51,7 @@
- - +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts index 0b24911ee6..e14dcc6ea7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ /// import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { DynamicValueSourceType, dynamicValueSourceTypeTranslationMap, @@ -28,7 +28,7 @@ import { AlarmConditionType } from '@shared/models/device.models'; @Component({ selector: 'tb-alarm-duration-predicate-value', templateUrl: './alarm-duration-predicate-value.component.html', - styleUrls: ['./alarm-duration-predicate-value.component.scss'], + styleUrls: [], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -68,7 +68,7 @@ export class AlarmDurationPredicateValueComponent implements ControlValueAccesso dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; - alarmDurationPredicateValueFormGroup: FormGroup; + alarmDurationPredicateValueFormGroup: UntypedFormGroup; dynamicMode = false; @@ -76,7 +76,7 @@ export class AlarmDurationPredicateValueComponent implements ControlValueAccesso private propagateChange = null; - constructor(private fb: FormBuilder) { + constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html index 6a64ba4800..be17201671 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html @@ -1,6 +1,6 @@ -
+

{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}

@@ -56,8 +56,7 @@ >
- - + diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss index fc2edaee5f..f0ac3daae1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts index 1df5a6bd4f..dcbceabcd5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { TranslateService } from '@ngx-translate/core'; @@ -52,7 +52,7 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent, - private fb: FormBuilder, + private fb: UntypedFormBuilder, public translate: TranslateService) { super(store, router, dialogRef); @@ -87,7 +87,7 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent { }; constructor(private dialog: MatDialog, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private translate: TranslateService) { } @@ -110,7 +110,7 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit return this.modelValue && this.modelValue.condition.length; } - public validate(c: FormControl) { + public validate(c: UntypedFormControl) { return this.conditionSet() ? null : { alarmRuleCondition: { valid: false, diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html index 69982686b1..5e7ca9b096 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html @@ -1,6 +1,6 @@ - +

{{ (readonly ? 'device-profile.schedule' : 'device-profile.edit-schedule') | translate }}

@@ -28,13 +28,9 @@
-
-
- - -
-
+ +
-
+
device-profile.snmp.please-add-communication-config
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss index 6dcf3642ad..9a93f17b23 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,8 @@ } :host ::ng-deep { - .mat-form-field.spec { - .mat-form-field-infix { + .mat-mdc-form-field.spec { + .mat-mdc-form-field-infix { width: 160px; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts index 6fe20f5e2f..7e0f8dd3b5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -18,16 +18,16 @@ import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, - FormArray, - FormBuilder, - FormGroup, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator, Validators } from '@angular/forms'; import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models'; -import { Subject, Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { isUndefinedOrNull } from '@core/utils'; import { takeUntil } from 'rxjs/operators'; @@ -52,34 +52,33 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On snmpSpecTypes = Object.values(SnmpSpecType); snmpSpecTypeTranslationMap = SnmpSpecTypeTranslationMap; - deviceProfileCommunicationConfig: FormGroup; + deviceProfileCommunicationConfig: UntypedFormGroup; @Input() disabled: boolean; private usedSpecType: SnmpSpecType[] = []; - private valueChange$: Subscription = null; - private destroy$ = new Subject(); + private destroy$ = new Subject(); private propagateChange = (v: any) => { }; - constructor(private fb: FormBuilder) { } + constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { this.deviceProfileCommunicationConfig = this.fb.group({ communicationConfig: this.fb.array([]) }); + this.deviceProfileCommunicationConfig.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); } ngOnDestroy() { - if (this.valueChange$) { - this.valueChange$.unsubscribe(); - } this.destroy$.next(); this.destroy$.complete(); } - communicationConfigFormArray(): FormArray { - return this.deviceProfileCommunicationConfig.get('communicationConfig') as FormArray; + get communicationConfigFormArray(): UntypedFormArray { + return this.deviceProfileCommunicationConfig.get('communicationConfig') as UntypedFormArray; } registerOnChange(fn: any): void { @@ -99,27 +98,27 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On } writeValue(communicationConfig: SnmpCommunicationConfig[]) { - if (this.valueChange$) { - this.valueChange$.unsubscribe(); - } - const communicationConfigControl: Array = []; - if (communicationConfig) { - communicationConfig.forEach((config) => { - communicationConfigControl.push(this.createdFormGroup(config)); - }); - } - this.deviceProfileCommunicationConfig.setControl('communicationConfig', this.fb.array(communicationConfigControl)); - if (!communicationConfig || !communicationConfig.length) { - this.addCommunicationConfig(); - } - if (this.disabled) { - this.deviceProfileCommunicationConfig.disable({emitEvent: false}); + if (communicationConfig?.length === this.communicationConfigFormArray.length) { + this.communicationConfigFormArray.patchValue(communicationConfig, {emitEvent: false}); } else { - this.deviceProfileCommunicationConfig.enable({emitEvent: false}); + const communicationConfigControl: Array = []; + if (communicationConfig) { + communicationConfig.forEach((config) => { + communicationConfigControl.push(this.createdFormGroup(config)); + }); + } + this.deviceProfileCommunicationConfig.setControl( + 'communicationConfig', this.fb.array(communicationConfigControl), {emitEvent: false} + ); + if (!communicationConfig || !communicationConfig.length) { + this.addCommunicationConfig(); + } + if (this.disabled) { + this.deviceProfileCommunicationConfig.disable({emitEvent: false}); + } else { + this.deviceProfileCommunicationConfig.enable({emitEvent: false}); + } } - this.valueChange$ = this.deviceProfileCommunicationConfig.valueChanges.subscribe(() => { - this.updateModel(); - }); this.updateUsedSpecType(); if (!this.disabled && !this.deviceProfileCommunicationConfig.valid) { this.updateModel(); @@ -133,16 +132,16 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On } public removeCommunicationConfig(index: number) { - this.communicationConfigFormArray().removeAt(index); + this.communicationConfigFormArray.removeAt(index); } get isAddEnabled(): boolean { - return this.communicationConfigFormArray().length !== Object.keys(SnmpSpecType).length; + return this.communicationConfigFormArray.length !== Object.keys(SnmpSpecType).length; } public addCommunicationConfig() { - this.communicationConfigFormArray().push(this.createdFormGroup()); + this.communicationConfigFormArray.push(this.createdFormGroup()); this.deviceProfileCommunicationConfig.updateValueAndValidity(); if (!this.deviceProfileCommunicationConfig.valid) { this.updateModel(); @@ -175,7 +174,7 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On }); } - private createdFormGroup(value?: SnmpCommunicationConfig): FormGroup { + private createdFormGroup(value?: SnmpCommunicationConfig): UntypedFormGroup { if (isUndefinedOrNull(value)) { value = { spec: this.getFirstUnusedSeverity(), diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html index e202370d21..94fe06137f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html @@ -1,6 +1,6 @@ - tenant-profile.tenant-profile +
-
+
relation.any-relation
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss index 0b4ae03f02..b18f226f4f 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,9 @@ .container{ width: 100%; - overflow: auto; } .header { - width: max-content; padding: 0 5px 5px; .cell { @@ -38,8 +36,9 @@ } .body { - width: max-content; padding: 0 5px 20px; + max-height: 300px; + overflow: auto; .row { padding-top: 5px; @@ -56,20 +55,3 @@ } } } - -:host ::ng-deep { - .tb-relation-filters { - .cell { - .mat-form-field { - .mat-form-field-infix { - border-top: none; - } - &.mat-form-field-appearance-standard { - .mat-form-field-flex { - padding-top: 0; - } - } - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts index 911aa4fe30..aa027c4846 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, - FormArray, - FormBuilder, - FormGroup, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; @@ -28,7 +28,8 @@ import { RelationEntityTypeFilter } from '@shared/models/relation.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-relation-filters', @@ -42,31 +43,41 @@ import { Subscription } from 'rxjs'; } ] }) -export class RelationFiltersComponent extends PageComponent implements ControlValueAccessor, OnInit { +export class RelationFiltersComponent extends PageComponent implements ControlValueAccessor, OnInit, OnDestroy { @Input() disabled: boolean; @Input() allowedEntityTypes: Array; - relationFiltersFormGroup: FormGroup; + relationFiltersFormGroup: UntypedFormGroup; + private destroy$ = new Subject(); private propagateChange = null; - private valueChangeSubscription: Subscription = null; - constructor(protected store: Store, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { super(store); } ngOnInit(): void { - this.relationFiltersFormGroup = this.fb.group({}); - this.relationFiltersFormGroup.addControl('relationFilters', - this.fb.array([])); + this.relationFiltersFormGroup = this.fb.group({ + relationFilters: this.fb.array([]) + }); + + this.relationFiltersFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { + this.updateModel(); + }); } - relationFiltersFormArray(): FormArray { - return this.relationFiltersFormGroup.get('relationFilters') as FormArray; + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + get relationFiltersFormArray(): UntypedFormArray { + return this.relationFiltersFormGroup.get('relationFilters') as UntypedFormArray; } registerOnChange(fn: any): void { @@ -81,32 +92,29 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa } writeValue(filters: Array): void { - if (this.valueChangeSubscription) { - this.valueChangeSubscription.unsubscribe(); - } - const relationFiltersControls: Array = []; - if (filters && filters.length) { - filters.forEach((filter) => { - relationFiltersControls.push(this.createRelationFilterFormGroup(filter)); - }); + if (filters?.length === this.relationFiltersFormArray.length) { + this.relationFiltersFormArray.patchValue(filters, {emitEvent: false}); + } else { + const relationFiltersControls: Array = []; + if (filters && filters.length) { + filters.forEach((filter) => { + relationFiltersControls.push(this.createRelationFilterFormGroup(filter)); + }); + } + this.relationFiltersFormGroup.setControl('relationFilters', this.fb.array(relationFiltersControls), {emitEvent: false}); } - this.relationFiltersFormGroup.setControl('relationFilters', this.fb.array(relationFiltersControls)); - this.valueChangeSubscription = this.relationFiltersFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); } public removeFilter(index: number) { - (this.relationFiltersFormGroup.get('relationFilters') as FormArray).removeAt(index); + (this.relationFiltersFormGroup.get('relationFilters') as UntypedFormArray).removeAt(index); } public addFilter() { - const relationFiltersFormArray = this.relationFiltersFormGroup.get('relationFilters') as FormArray; const filter: RelationEntityTypeFilter = { relationType: null, entityTypes: [] }; - relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter)); + this.relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter)); } private createRelationFilterFormGroup(filter: RelationEntityTypeFilter): AbstractControl { diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html index 5c2c529815..c96b22a1cc 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html @@ -1,6 +1,6 @@
-
- +
+
@@ -52,7 +52,7 @@
- +
- +
{{ 'relation.selected-relations' | translate:{count: dataSource.selection.selected.length} }} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss index 453c4ab897..e39d275075 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,13 @@ width: 100%; height: 100%; background: #fff; + overflow: hidden; + + &.tb-outlined-border { + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + } .mat-toolbar-tools{ min-height: auto; @@ -67,29 +74,3 @@ } } } - -:host ::ng-deep { - .mat-sort-header-sorted .mat-sort-header-arrow { - opacity: 1 !important; - } - mat-form-field.tb-relation-direction { - font-size: 16px; - width: 200px; - - .mat-form-field-wrapper { - padding-bottom: 0; - } - - .mat-form-field-underline { - bottom: 0; - } - - @media #{$mat-xs} { - width: 100%; - - .mat-form-field-infix { - width: auto !important; - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts index 6b2649ec9a..04a3f87486 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.html b/ui-ngx/src/app/modules/home/components/router-tabs.component.html new file mode 100644 index 0000000000..afcc4d9fa9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.html @@ -0,0 +1,37 @@ + + diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss b/ui-ngx/src/app/modules/home/components/router-tabs.component.scss similarity index 52% rename from ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss rename to ui-ngx/src/app/modules/home/components/router-tabs.component.scss index 813272928c..8fe2c2b305 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,37 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../../../scss/constants'; - :host { - flex: 1; display: flex; - justify-content: flex-start; - min-width: 150px; -} - -:host ::ng-deep { - tb-entity-subtype-select { - width: 100%; - - mat-form-field { - font-size: 16px; - - .mat-form-field-wrapper { - padding-bottom: 0; - } + width: 100%; + height: 100%; - .mat-form-field-underline { - bottom: 0; + .mat-mdc-tab-nav-bar.tb-router-tabs { + a.mat-mdc-tab-link { + display: inline-flex; + align-items: center; + overflow: hidden; + line-height: 40px; + min-width: 200px; + border-bottom: none; + mat-icon { + margin-right: 8px; + margin-left: 0; } - - @media #{$mat-xs} { - width: 100%; - - .mat-form-field-infix { - width: auto !important; - } + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } } } + + .tb-router-tabs-content { + overflow: auto; + position: relative; + height: 100%; + } } diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts new file mode 100644 index 0000000000..65c0074227 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts @@ -0,0 +1,125 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, ComponentRef, OnInit, Type, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { MenuService } from '@core/services/menu.service'; +import { distinctUntilChanged, filter, map, mergeMap, take } from 'rxjs/operators'; +import { merge } from 'rxjs'; +import { MenuSection } from '@core/services/menu.models'; +import { ActiveComponentService } from '@core/services/active-component.service'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; + +@Component({ + selector: 'tb-router-tabs', + templateUrl: './router-tabs.component.html', + styleUrls: ['./router-tabs.component.scss'] +}) +export class RouterTabsComponent extends PageComponent implements OnInit { + + @ViewChild('tabsHeaderComponent', {static: true}) tabsHeaderComponentAnchor: TbAnchorComponent; + + tabsHeaderComponentRef: ComponentRef; + + hideCurrentTabs = false; + + tabs$ = merge(this.menuService.menuSections(), + this.router.events.pipe( + filter((event) => event instanceof NavigationEnd ), + distinctUntilChanged()) + ).pipe( + mergeMap(() => this.menuService.menuSections().pipe(take(1))), + map((sections) => this.buildTabs(this.activatedRoute, sections)) + ); + + constructor(protected store: Store, + private activatedRoute: ActivatedRoute, + public router: Router, + private menuService: MenuService, + private activeComponentService: ActiveComponentService) { + super(store); + } + + ngOnInit() { + this.activatedRoute.data.subscribe( + (data) => this.buildTabsHeaderComponent(data) + ); + } + + activeComponentChanged(activeComponent: any) { + this.activeComponentService.setCurrentActiveComponent(activeComponent); + let snapshot = this.router.routerState.snapshot.root; + this.hideCurrentTabs = false; + let found = false; + while (snapshot.children.length) { + if (snapshot.component && snapshot.component === RouterTabsComponent) { + if (this.activatedRoute.snapshot === snapshot) { + found = true; + } else if (found) { + this.hideCurrentTabs = true; + break; + } + } + snapshot = snapshot.children[0]; + } + } + + private buildTabs(activatedRoute: ActivatedRoute, sections: MenuSection[]): Array { + const sectionPath = '/' + activatedRoute.pathFromRoot.map(r => r.snapshot.url) + .filter(f => !!f[0]).map(f => f.map(f1 => f1.path).join('/')).join('/'); + const found = this.findRootSection(sections, sectionPath); + if (found) { + const rootPath = sectionPath.substring(0, sectionPath.length - found.path.length); + const isRoot = rootPath === ''; + const tabs: Array = found ? found.pages.filter(page => !page.disabled && (!page.rootOnly || isRoot)) : []; + return tabs.map((tab) => ({...tab, path: rootPath + tab.path})); + } else { + return []; + } + } + + private findRootSection(sections: MenuSection[], sectionPath: string): MenuSection { + for (const section of sections) { + if (sectionPath.endsWith(section.path)) { + return section; + } + if (section.pages?.length) { + const found = this.findRootSection(section.pages, sectionPath); + if (found) { + return found; + } + } + } + return null; + } + + private buildTabsHeaderComponent(snapshotData: any) { + const viewContainerRef = this.tabsHeaderComponentAnchor.viewContainerRef; + if (this.tabsHeaderComponentRef) { + this.tabsHeaderComponentRef.destroy(); + this.tabsHeaderComponentRef = null; + viewContainerRef.clear(); + } + const tabsHeaderComponentType: Type = snapshotData.routerTabsHeaderComponent; + if (tabsHeaderComponentType) { + this.tabsHeaderComponentRef = viewContainerRef.createComponent(tabsHeaderComponentType, {index: 0}); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.html index fd99e1bc54..e282d62f49 100644 --- a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.html @@ -1,6 +1,6 @@ - {{ labelText | translate }} + @@ -54,4 +55,7 @@ {{ 'rulechain.rulechain-required' | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts index 3db424012f..f371f4201f 100644 --- a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ /// import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { catchError, debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -43,18 +43,19 @@ import { RuleChainType } from '@app/shared/models/rule-chain.models'; }) export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnInit { - selectRuleChainFormGroup: FormGroup; - - ruleChainLabel = 'rulechain.rulechain'; + selectRuleChainFormGroup: UntypedFormGroup; modelValue: string | null; @Input() - labelText: string; + labelText: string = 'rulechain.rulechain'; @Input() requiredText: string; + @Input() + ruleChainType: RuleChainType = RuleChainType.CORE; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; @@ -83,7 +84,7 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI public truncate: TruncatePipe, private entityService: EntityService, private ruleChainService: RuleChainService, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { this.selectRuleChainFormGroup = this.fb.group({ ruleChainId: [null] }); @@ -191,9 +192,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI fetchRuleChain(searchText?: string): Observable>> { this.searchText = searchText; - // @voba: at the moment device profiles are not supported by edge, so 'core' hardcoded return this.entityService.getEntitiesByNameFilter(EntityType.RULE_CHAIN, searchText, - 50, RuleChainType.CORE, {ignoreLoading: true}).pipe( + 50, this.ruleChainType, {ignoreLoading: true}).pipe( catchError(() => of([])) ); } diff --git a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts index 4052958e86..865e7cf224 100644 --- a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,6 +19,9 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; import { SHARED_HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; +import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; +import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component'; +import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.component'; @NgModule({ providers: [ @@ -26,14 +29,20 @@ import { SHARED_HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; ], declarations: [ - AlarmDetailsDialogComponent + AlarmDetailsDialogComponent, + AlarmCommentComponent, + AlarmCommentDialogComponent, + AlarmAssigneeComponent ], imports: [ CommonModule, SharedModule ], exports: [ - AlarmDetailsDialogComponent + AlarmDetailsDialogComponent, + AlarmCommentComponent, + AlarmCommentDialogComponent, + AlarmAssigneeComponent ] }) export class SharedHomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html index e72cd7f077..8b29a5fbdc 100644 --- a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html @@ -1,6 +1,6 @@
- + admin.smpp-provider.smpp-version @@ -32,7 +32,7 @@ {{'admin.smpp-provider.smpp-host-required' | translate}} - + admin.smpp-provider.smpp-port diff --git a/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts index 605af2cb51..f4e305ef1c 100644 --- a/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ /// import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { AwsSnsSmsProviderConfiguration, BindTypes, @@ -46,7 +46,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; }) export class SmppSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit{ - constructor(private fb: FormBuilder) { + constructor(private fb: UntypedFormBuilder) { } private requiredValue: boolean; @@ -61,7 +61,7 @@ export class SmppSmsProviderConfigurationComponent implements ControlValueAcces @Input() disabled: boolean; - smppSmsProviderConfigurationFormGroup: FormGroup; + smppSmsProviderConfigurationFormGroup: UntypedFormGroup; smppVersions = smppVersions; diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html index f4d9d0d1c9..77b22386a1 100644 --- a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html @@ -1,6 +1,6 @@ - + admin.number-from diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts index 556828c138..4627089cd3 100644 --- a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ /// import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; @@ -39,7 +39,7 @@ import { }) export class TwilioSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { - twilioSmsProviderConfigurationFormGroup: FormGroup; + twilioSmsProviderConfigurationFormGroup: UntypedFormGroup; phoneNumberPatternTwilio = phoneNumberPatternTwilio; @@ -60,7 +60,7 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce private propagateChange = (v: any) => { }; constructor(private store: Store, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { } registerOnChange(fn: any): void { diff --git a/ui-ngx/src/app/modules/home/components/tokens.ts b/ui-ngx/src/app/modules/home/components/tokens.ts index 62b580432f..5ddd9fe661 100644 --- a/ui-ngx/src/app/modules/home/components/tokens.ts +++ b/ui-ngx/src/app/modules/home/components/tokens.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html index 8336eb728d..b619e9d846 100644 --- a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html @@ -1,6 +1,6 @@
- - -
- admin.auto-commit-settings - -
-
-
+ + + + admin.auto-commit-settings + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss index b8bb1ee760..36617d9ab9 100644 --- a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ :host { - mat-card.auto-commit-settings { + .mat-mdc-card.auto-commit-settings { margin: 8px; .mat-divider { position: relative; diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts index 980d89db4f..d94cec9457 100644 --- a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ import { Component, OnInit } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { AbstractControl, FormArray, FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; +import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, FormGroupDirective, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AdminService } from '@core/http/admin.service'; @@ -36,7 +36,7 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; }) export class AutoCommitSettingsComponent extends PageComponent implements OnInit { - autoCommitSettingsForm: FormGroup; + autoCommitSettingsForm: UntypedFormGroup; settings: AutoCommitSettings = null; entityTypes = EntityType; @@ -48,7 +48,7 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit private dialogService: DialogService, private sanitizer: DomSanitizer, private translate: TranslateService, - public fb: FormBuilder) { + public fb: UntypedFormBuilder) { super(store); } @@ -76,8 +76,8 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit this.isReadOnly = this.adminService.getRepositorySettingsInfo().pipe(map(settings => settings.readOnly)); } - entityTypesFormGroupArray(): FormGroup[] { - return (this.autoCommitSettingsForm.get('entityTypes') as FormArray).controls as FormGroup[]; + entityTypesFormGroupArray(): UntypedFormGroup[] { + return (this.autoCommitSettingsForm.get('entityTypes') as UntypedFormArray).controls as UntypedFormGroup[]; } entityTypesFormGroupExpanded(entityTypeControl: AbstractControl): boolean { @@ -89,17 +89,17 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit } public removeEntityType(index: number) { - (this.autoCommitSettingsForm.get('entityTypes') as FormArray).removeAt(index); + (this.autoCommitSettingsForm.get('entityTypes') as UntypedFormArray).removeAt(index); this.autoCommitSettingsForm.markAsDirty(); } public addEnabled(): boolean { - const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray; + const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as UntypedFormArray; return entityTypesArray.length < exportableEntityTypes.length; } public addEntityType() { - const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray; + const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as UntypedFormArray; const config: AutoVersionCreateConfig = { branch: null, saveAttributes: true, @@ -119,7 +119,7 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit } public removeAll() { - const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray; + const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as UntypedFormArray; entityTypesArray.clear(); this.autoCommitSettingsForm.updateValueAndValidity(); this.autoCommitSettingsForm.markAsDirty(); @@ -187,7 +187,7 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit }); } - private prepareEntityTypesFormArray(settings: AutoCommitSettings | null): FormArray { + private prepareEntityTypesFormArray(settings: AutoCommitSettings | null): UntypedFormArray { const entityTypesControls: Array = []; if (settings) { for (const entityType of Object.keys(settings)) { diff --git a/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html b/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html index 317d6e9cb6..d19ed2b51d 100644 --- a/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html @@ -1,6 +1,6 @@ -

{{ 'version-control.create-entities-version' | translate }}

@@ -70,7 +69,7 @@
-
+
- +
diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss index 158f7ba5f4..80df7d9d79 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,9 @@ * limitations under the License. */ :host { - mat-card.repository-settings { + .mat-mdc-card.repository-settings { margin: 8px; } - mat-checkbox { - padding-bottom: 16px; - } .fields-group { padding: 0 16px 8px; margin-bottom: 10px; @@ -37,8 +34,3 @@ } } } -:host ::ng-deep { - .mat-checkbox-layout { - white-space: normal !important; - } -} diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts index 1270acf150..b42bca156d 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, FormGroupDirective, Validators } from '@angular/forms'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AdminService } from '@core/http/admin.service'; @@ -48,7 +48,7 @@ export class RepositorySettingsComponent extends PageComponent implements OnInit @Input() popoverComponent: TbPopoverComponent; - repositorySettingsForm: FormGroup; + repositorySettingsForm: UntypedFormGroup; settings: RepositorySettings = null; repositoryAuthMethod = RepositoryAuthMethod; @@ -66,7 +66,7 @@ export class RepositorySettingsComponent extends PageComponent implements OnInit private dialogService: DialogService, private translate: TranslateService, private cd: ChangeDetectorRef, - public fb: FormBuilder) { + public fb: UntypedFormBuilder) { super(store); } diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html index eaf80fd664..7b14dde065 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html @@ -1,6 +1,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss index 611394ee12..4eebb52161 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,10 +33,10 @@ box-sizing: border-box; } - mat-tab-group { - .mat-tab-body-wrapper { + .mat-mdc-tab-group { + .mat-mdc-tab-body-wrapper { height: 100%; - mat-tab-body { + .mat-mdc-tab-body { height: 100%; & > div { height: 100%; @@ -81,20 +81,20 @@ &.tb-fullscreen-editor { position: relative; right: 0; - .mat-button { + /* .mat-mdc-button { .mat-icon { margin-right: 5px; } - } + } */ } - .mat-button { + /* .mat-mdc-button { min-width: 36px; padding: 0; .mat-icon { margin-right: 0; } - } + } */ } .tb-custom-action-editor { diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts index 7c36347f2a..5c2111c76e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ /// limitations under the License. /// -// tslint:disable-next-line:no-reference +// eslint-disable-next-line @typescript-eslint/triple-slash-reference /// import { diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html index 63dc06c1d4..c3d0141d20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html @@ -1,6 +1,6 @@
-
- +
+
widget-config.actions @@ -36,7 +36,7 @@
- +
+ close
- - close -
- - - + + +
+ - - - notifications - - - - - - - - - timeline - - + + notifications + + + timeline + @@ -130,31 +137,22 @@ {{'entity.create-new-key' | translate }} - - notifications - - - - - - - - - timeline - + notifications + + + timeline
@@ -167,3 +165,19 @@
{{ maxDataKeysText() }}
+ + + f() + + + + + + + + {{ key.aggregationType }}({{ key.name }}) + + + {{key.name}} + + diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.models.ts index a04221fd3b..9407145376 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss index 334f533dd4..a0b909af1a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,62 +14,107 @@ * limitations under the License. */ -.mat-chip.mat-standard-chip.tb-datakey-chip { - overflow: hidden; +.tb-datakeys-container { + display: flex; + flex-wrap: wrap; + width: 100%; - .tb-attribute-chip { - max-width: 100%; - color: rgb(66, 66, 66); - font-weight: normal; - font-size: 16px; + input.tb-dragging { + display: none; + } - .tb-chip-drag-handle { - cursor: move; + .mat-mdc-chip.mat-mdc-standard-chip.tb-datakey-chip { + overflow: hidden; + line-height: 20px; + height: 32px; - mat-icon { - pointer-events: none; + .mat-mdc-chip-action { + overflow: hidden; + .mat-mdc-chip-action-label { + overflow: hidden; } } - - .tb-chip-labels { - display: flex; - flex-direction: row; - align-items: flex-end; - min-width: 0; - .tb-chip-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + .tb-attribute-chip { + max-width: 100%; + color: rgb(66, 66, 66); + font-weight: normal; + font-size: 16px; + .tb-chip-drag-handle { + cursor: move; + mat-icon { + pointer-events: none; + margin-right: 4px; + margin-left: 4px; + vertical-align: bottom; + } } - - .tb-chip-separator { - white-space: pre; + .tb-chip-labels { + display: flex; + flex-direction: row; + align-items: center; + min-width: 0; + .tb-chip-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + .mat-icon.tb-datakey-icon { + margin-right: 4px; + margin-left: 4px; + } + .tb-agg-func { + font-style: italic; + color: #0c959c; + } + } + .tb-chip-separator { + white-space: pre; + } + } + .mat-mdc-chip-remove.mat-icon { + width: 24px; + min-width: 24px; + height: 24px; + font-size: 24px; + margin-right: 4px; + color: inherit; + opacity: inherit; + padding-left: 0; } } - .mat-chip-remove.mat-icon { - width: 24px; - height: 24px; - font-size: 24px; - margin-left: 0; - color: inherit; - opacity: inherit; + &.tb-datakey-chip-dnd-placeholder { + min-width: 120px; + border: 2px dashed rgba(0, 0, 0, 0.2); + } + &.tb-chip-dragging { + display: none; + } + .tb-dragging-chip-image-fill { + background-color: rgba(0,0,0,0.3); + border-radius: var(--mdc-chip-container-shape-radius, 16px 16px 16px 16px); + display: none; + pointer-events: none; + } + .tb-dragging-chip-image { + background-color: var(--mdc-chip-elevated-container-color, transparent); + border-radius: var(--mdc-chip-container-shape-radius, 16px 16px 16px 16px); + overflow: hidden; + height: 32px; + line-height: 20px; + .tb-dragging-chip-image-fill { + display: block; + } } } } -:host ::ng-deep { - .mat-form-field-flex { - padding-top: 0; - .mat-form-field-infix { - border-top: 0; - } +.mat-icon.tb-datakey-icon { + vertical-align: middle; + & > svg { + vertical-align: initial; } - - .tb-chip-label { - .tb-agg-func { - font-style: italic; - color: #0c959c; - } + &.new-key { + margin-left: 8px; + margin-right: 8px; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index 940e4c4c9b..757f838fe4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -16,25 +16,27 @@ import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { - AfterViewInit, + ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnChanges, OnInit, + Renderer2, SimpleChanges, SkipSelf, - ViewChild + ViewChild, + ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, - FormBuilder, - FormControl, - FormGroup, FormGroupDirective, NG_VALUE_ACCESSOR, NgForm, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, Validators } from '@angular/forms'; import { Observable, of } from 'rxjs'; @@ -43,7 +45,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { MatAutocomplete } from '@angular/material/autocomplete'; -import { MatChipInputEvent, MatChipList } from '@angular/material/chips'; +import { MatChipGrid, MatChipInputEvent, MatChipRow } from '@angular/material/chips'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKey, DatasourceType, JsonSettingsSchema, Widget, widgetType } from '@shared/models/widget.models'; @@ -59,11 +61,11 @@ import { DataKeyConfigDialogComponent, DataKeyConfigDialogData } from '@home/components/widget/data-key-config-dialog.component'; -import { deepClone } from '@core/utils'; -import { MatChipDropEvent } from '@app/shared/components/mat-chip-draggable.directive'; +import { deepClone, guid, isUndefined } from '@core/utils'; import { Dashboard } from '@shared/models/dashboard.models'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { AggregationType } from '@shared/models/time/time.models'; +import { DndDropEvent } from 'ngx-drag-drop/lib/dnd-dropzone.directive'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; @Component({ selector: 'tb-data-keys', @@ -79,15 +81,16 @@ import { AggregationType } from '@shared/models/time/time.models'; provide: ErrorStateMatcher, useExisting: DataKeysComponent } - ] + ], + encapsulation: ViewEncapsulation.None }) -export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges, ErrorStateMatcher { +export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChanges, ErrorStateMatcher { datasourceTypes = DatasourceType; widgetTypes = widgetType; dataKeyTypes = DataKeyType; - keysListFormGroup: FormGroup; + keysListFormGroup: UntypedFormGroup; modelValue: Array | null; @@ -145,7 +148,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie @ViewChild('keyInput') keyInput: ElementRef; @ViewChild('keyAutocomplete') matAutocomplete: MatAutocomplete; - @ViewChild('chipList') chipList: MatChipList; + @ViewChild('chipList') chipList: MatChipGrid; keys: Array = []; filteredKeys: Observable>; @@ -161,6 +164,11 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie requiredText: string; searchText = ''; + + dndId = guid(); + + dragIndex: number; + private latestSearchTextResult: Array = null; private fetchObservable$: Observable> = null; @@ -174,8 +182,9 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie private utils: UtilsService, private dialogs: DialogService, private dialog: MatDialog, - private fb: FormBuilder, - private sanitizer: DomSanitizer, + private fb: UntypedFormBuilder, + private cd: ChangeDetectorRef, + private renderer: Renderer2, public truncate: TruncatePipe) { } @@ -250,7 +259,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie this.secondaryPlaceholder = '+' + this.translate.instant('datakey.latest-key-function'); } else if (this.widgetType === widgetType.alarm) { this.placeholder = this.translate.instant('datakey.alarm-key-functions'); - this.secondaryPlaceholder = '+' + this.translate.instant('alarm-key-function'); + this.secondaryPlaceholder = '+' + this.translate.instant('datakey.alarm-key-function'); } else { this.placeholder = this.translate.instant('datakey.timeseries-key-functions'); this.secondaryPlaceholder = '+' + this.translate.instant('datakey.timeseries-key-function'); @@ -314,14 +323,12 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie } } - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); const customErrorState = this.required && !this.modelValue; return originalErrorState || customErrorState; } - ngAfterViewInit(): void {} - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { @@ -395,12 +402,24 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie } } - onChipDrop(event: MatChipDropEvent) { - const from = event.from; - const to = event.to; - this.keys.splice(to, 0, this.keys.splice(from, 1)[0]); + chipDragStart(index: number, chipRow: MatChipRow, placeholderChipRow: MatChipRow) { + this.renderer.setStyle(placeholderChipRow._elementRef.nativeElement, 'width', chipRow._elementRef.nativeElement.offsetWidth + 'px'); + this.dragIndex = index; + } + + chipDragEnd() { + this.dragIndex = -1; + } + + onChipDrop(event: DndDropEvent) { + let index = event.index; + if (isUndefined(index)) { + index = this.keys.length; + } + moveItemInArray(this.keys, this.dragIndex, index); this.keysListFormGroup.get('keys').setValue(this.keys); - this.modelValue.splice(to, 0, this.modelValue.splice(from, 1)[0]); + moveItemInArray(this.modelValue, this.dragIndex, index); + this.dragIndex = -1; this.propagateChange(this.modelValue); } @@ -450,34 +469,13 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie return key ? key.name : undefined; } - displayDataKeyNameFn(key: DataKey): SafeHtml { - let keyName = key.name; - if (this.widgetType === widgetType.latest && key.type === DataKeyType.timeseries - && key.aggregationType && key.aggregationType !== AggregationType.NONE) { - let aggFuncName: string; - switch (key.aggregationType) { - case AggregationType.MIN: - aggFuncName = 'MIN'; - break; - case AggregationType.MAX: - aggFuncName = 'MAX'; - break; - case AggregationType.AVG: - aggFuncName = 'AVG'; - break; - case AggregationType.SUM: - aggFuncName = 'SUM'; - break; - case AggregationType.COUNT: - aggFuncName = 'COUNT'; - break; - } - keyName = `${aggFuncName}(${keyName})`; - } - if (this.datasourceType !== DatasourceType.function && key.postFuncBody) { - keyName = `f(${keyName})`; - } - return this.sanitizer.bypassSecurityTrustHtml(`${keyName}`); + dataKeyHasAggregation(key: DataKey): boolean { + return this.widgetType === widgetType.latest && key.type === DataKeyType.timeseries + && key.aggregationType && key.aggregationType !== AggregationType.NONE; + } + + dataKeyHasPostprocessing(key: DataKey): boolean { + return this.datasourceType !== DatasourceType.function && !!key.postFuncBody; } private fetchKeys(searchText?: string): Observable> { diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts index be717873da..d9ab65813f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts index ce5b3badb8..092d91b0f8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { PageComponent } from '@shared/components/page.component'; import { CustomDialogContainerComponent } from './custom-dialog-container.component'; -import { FormBuilder, Validators } from '@angular/forms'; +import { UntypedFormBuilder, Validators } from '@angular/forms'; import { TbInject } from '@shared/decorators/tb-inject'; export const CUSTOM_DIALOG_DATA = new InjectionToken('ConfigDialogData'); @@ -32,7 +32,7 @@ export interface CustomDialogData { } @Directive() -// tslint:disable-next-line:directive-class-suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix export class CustomDialogComponent extends PageComponent { [key: string]: any; @@ -40,7 +40,7 @@ export class CustomDialogComponent extends PageComponent { constructor(@TbInject(Store) protected store: Store, @TbInject(Router) protected router: Router, @TbInject(MatDialogRef) public dialogRef: MatDialogRef, - @TbInject(FormBuilder) public fb: FormBuilder, + @TbInject(UntypedFormBuilder) public fb: UntypedFormBuilder, @TbInject(CUSTOM_DIALOG_DATA) public data: CustomDialogData) { super(store); // @ts-ignore diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts index 4de6abdd31..73b67017ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog-token.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog-token.ts index 5d0ee4733f..14b9af98ad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog-token.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog-token.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html index 4936cb5ba6..36837414ed 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html @@ -1,6 +1,6 @@
- +
-
- +
{{ translate.get('alarm.selected-alarms', @@ -45,13 +45,13 @@ - + + - + - +
-
-
-
@@ -59,18 +60,18 @@
- + - +
- - - - -
-
- - - check - - - - {{ 'device.wizard.device-details' | translate}} -
- - device.name - - - {{ 'device.name-required' | translate }} - - - {{ 'device.name-max-length' | translate }} - - - - device.label - - - {{ 'device.label-max-length' | translate }} - - -
- - - device.wizard.existing-device-profile - - - device.wizard.new-device-profile - - -
- - - - device-profile.new-device-profile-name - - - {{ 'device-profile.new-device-profile-name-required' | translate }} - - -
-
- - -
-
- - -
+ +

device.add-device-text

+ +
+ +
+ + +
+
+ + + check + + + + {{ 'device.wizard.device-details' | translate}} +
+ + device.name + + + {{ 'device.name-required' | translate }} + + + {{ 'device.name-max-length' | translate }} + + + + device.label + + + {{ 'device.label-max-length' | translate }} + + +
+ + + device.wizard.existing-device-profile + + + device.wizard.new-device-profile + + +
+ + + + device-profile.new-device-profile-name + + + {{ 'device-profile.new-device-profile-name-required' | translate }} + +
-
- - {{ 'device.is-gateway' | translate }} - - - {{ 'device.overwrite-activity-time' | translate }} - +
+ +
- - device.description - - -
- -
- -
- {{ 'device-profile.transport-configuration' | translate }} - device-profile.transport-type - - - {{deviceTransportTypeTranslations.get(type) | translate}} - - - - {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}} - - - {{ 'device-profile.transport-type-required' | translate }} - - - - -
-
- -
- {{'device-profile.alarm-rules-with-count' | translate: - {count: alarmRulesFormGroup.get('alarms').value ? - alarmRulesFormGroup.get('alarms').value.length : 0} }} - - -
-
- -
- {{ 'device-profile.device-provisioning' | translate }} - - -
-
- - {{ 'device.credentials' | translate }} -
- {{ 'device.wizard.add-credentials' | translate }} - - -
-
- - {{ 'customer.customer' | translate }} -
- - -
-
-
-
-
+
+ + +
+
+
+ + {{ 'device.is-gateway' | translate }} + + + {{ 'device.overwrite-activity-time' | translate }} + +
+ + device.description + + +
+ +
+ +
+ {{ 'device-profile.transport-configuration' | translate }} + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}} + + + {{ 'device-profile.transport-type-required' | translate }} + + + + +
+
+ +
+ {{'device-profile.alarm-rules-with-count' | translate: + {count: alarmRulesFormGroup.get('alarms').value ? + alarmRulesFormGroup.get('alarms').value.length : 0} }} + + +
+
+ +
+ {{ 'device-profile.device-provisioning' | translate }} + + +
+
+ + {{ 'device.credentials' | translate }} +
+ {{ 'device.wizard.add-credentials' | translate }} + + +
+
+ + {{ 'customer.customer' | translate }} +
+ + +
+
+
+
+
+
@@ -191,8 +191,8 @@ [disabled]="(isLoading$ | async)" (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}
- -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index ffeaad5acd..0fe18467fd 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,18 @@ */ @import "../../../../../scss/constants"; -:host-context(.tb-fullscreen-dialog .mat-dialog-container) { +:host { + height: 100%; + display: grid; + + .dialog-actions-row { + padding: 8px; + } +} + +:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) { @media #{$mat-lt-sm} { - .mat-dialog-content { + .mat-mdc-dialog-content { max-height: 75vh; } } @@ -28,7 +37,7 @@ } :host ::ng-deep { - .mat-dialog-content { + .mat-mdc-dialog-content { display: flex; flex-direction: column; height: 100%; @@ -36,37 +45,21 @@ .mat-stepper-horizontal { display: flex; - flex-direction: column; height: 100%; overflow: hidden; - @media #{$mat-lt-sm} { - .mat-step-label { - white-space: normal; - overflow: visible; - .mat-step-text-label { - overflow: visible; - } - } + + .mat-horizontal-stepper-wrapper { + flex: 1 1 100%; } + .mat-horizontal-content-container { - height: 530px; + height: 680px; max-height: 100%; width: 100%;; overflow-y: auto; + scrollbar-gutter: stable; @media #{$mat-gt-sm} { - min-width: 800px; - } - } - .mat-horizontal-stepper-content[aria-expanded=true] { - height: 100%; - form { - height: 100%; - } - } - - tb-device-profile-autocomplete, tb-queue-autocomplete{ - .mat-form-field-wrapper{ - width: 180px !important; + min-width: 500px; } } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 694deabd96..77916d379c 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { Component, Inject, OnDestroy, SkipSelf, ViewChild } from '@angular/core import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { @@ -33,7 +33,7 @@ import { deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; -import { MatHorizontalStepper } from '@angular/material/stepper'; +import { MatStepper } from '@angular/material/stepper'; import { AddEntityDialogData } from '@home/models/entity/entity-component.models'; import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; @@ -59,7 +59,7 @@ import { deepTrim } from '@core/utils'; export class DeviceWizardDialogComponent extends DialogComponent implements OnDestroy, ErrorStateMatcher { - @ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatHorizontalStepper; + @ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatStepper; selectedIndex = 0; @@ -75,19 +75,19 @@ export class DeviceWizardDialogComponent extends deviceTransportTypeHints = deviceTransportTypeHintMap; - deviceWizardFormGroup: FormGroup; + deviceWizardFormGroup: UntypedFormGroup; - transportConfigFormGroup: FormGroup; + transportConfigFormGroup: UntypedFormGroup; - alarmRulesFormGroup: FormGroup; + alarmRulesFormGroup: UntypedFormGroup; - provisionConfigFormGroup: FormGroup; + provisionConfigFormGroup: UntypedFormGroup; - credentialsFormGroup: FormGroup; + credentialsFormGroup: UntypedFormGroup; - customerFormGroup: FormGroup; + customerFormGroup: UntypedFormGroup; - labelPosition: MatHorizontalStepper['labelPosition'] = 'end'; + labelPosition: MatStepper['labelPosition'] = 'end'; serviceType = ServiceType.TB_RULE_ENGINE; @@ -102,7 +102,7 @@ export class DeviceWizardDialogComponent extends private deviceProfileService: DeviceProfileService, private deviceService: DeviceService, private breakpointObserver: BreakpointObserver, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { super(store, router, dialogRef); this.deviceWizardFormGroup = this.fb.group({ name: ['', [Validators.required, Validators.maxLength(255)]], @@ -205,7 +205,7 @@ export class DeviceWizardDialogComponent extends this.subscriptions.forEach(s => s.unsubscribe()); } - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); const customErrorState = !!(control && control.invalid); return originalErrorState || customErrorState; diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html index bc8aadedae..2588c38ba3 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html @@ -1,6 +1,6 @@ - - {{section.icon}} - + + {{section.icon}} + {{section.name | translate}} diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.scss b/ui-ngx/src/app/modules/home/menu/menu-link.component.scss index da8df4b469..66df772d2d 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-link.component.scss +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.ts b/ui-ngx/src/app/modules/home/menu/menu-link.component.ts index cae14d32b6..f2d43248bd 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-link.component.ts +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html index 0635205581..de4f16b163 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html @@ -1,6 +1,6 @@ - - {{section.icon}} - + + {{section.icon}} + {{section.name | translate}} + [ngClass]="{'tb-toggled' : section.opened}">
  • diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss index a076fa77d6..93d0276639 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ .tb-toggle-icon { display: inline-block; width: 15px; + min-width: 15px; margin: auto 0 auto auto; background-size: 100% auto; transition: transform .3s ease-in-out; @@ -32,11 +33,11 @@ z-index: 1; overflow: hidden; - transition: .75s cubic-bezier(.35, 0, .25, 1); + transition: .45s cubic-bezier(.35, 0, .25, 1); transition-property: height; - a.mat-button { + a.mat-mdc-button { padding: 0 16px 0 32px; font-weight: 500; text-transform: none !important; diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts index 76a818c16b..70d711469a 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -17,6 +17,9 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { MenuSection } from '@core/services/menu.models'; import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ActionPreferencesUpdateOpenedMenuSection } from '@core/auth/auth.actions'; @Component({ selector: 'tb-menu-toggle', @@ -28,24 +31,27 @@ export class MenuToggleComponent implements OnInit { @Input() section: MenuSection; - constructor(private router: Router) { + constructor(private router: Router, + private store: Store) { } ngOnInit() { } - sectionActive(): boolean { - return this.router.isActive(this.section.path, false); - } - sectionHeight(): string { - if (this.router.isActive(this.section.path, false)) { - return this.section.height; + if (this.section.opened) { + return this.section.pages.length * 40 + 'px'; } else { return '0px'; } } + toggleSection(event: MouseEvent) { + event.stopPropagation(); + this.section.opened = !this.section.opened; + this.store.dispatch(new ActionPreferencesUpdateOpenedMenuSection({path: this.section.path, opened: this.section.opened})); + } + trackBySectionPages(index: number, section: MenuSection){ return section.id; } diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.html b/ui-ngx/src/app/modules/home/menu/side-menu.component.html index 8a9f07f3c9..b41b05ca76 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.html +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.html @@ -1,6 +1,6 @@
    - - -
    - admin.general-settings -
    -
    + + + +
    + admin.general-settings +
    +
    +
    - +
    @@ -35,10 +37,10 @@ {{ 'admin.base-url-required' | translate }} - + {{ 'admin.prohibit-different-url' | translate }} -
    admin.prohibit-different-url-hint
    +
    admin.prohibit-different-url-hint
    - -
    -
    - -
    -
    -
    + +
    + + + +
    + +
    +
    + + +
    + + +
    +
    +
    +
    + + + + + admin.slack-settings + + +
    +
    + + +
    + +
    +
    +
    + + admin.slack-api-token + + +
    +
    + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss index da8df4b469..66df772d2d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts index bfd1a38e14..e56f02c4e4 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; @@ -25,43 +25,62 @@ import { AdminService } from '@core/http/admin.service'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { MatDialog } from '@angular/material/dialog'; import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component'; +import { NotificationSettings } from '@shared/models/notification.models'; +import { deepTrim, isEmptyStr } from '@core/utils'; +import { NotificationService } from '@core/http/notification.service'; +import { Authority } from '@shared/models/authority.enum'; +import { AuthUser } from '@shared/models/user.model'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; @Component({ selector: 'tb-sms-provider', templateUrl: './sms-provider.component.html', styleUrls: ['./sms-provider.component.scss', './settings-card.scss'] }) -export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm { +export class SmsProviderComponent extends PageComponent implements HasConfirmForm { smsProvider: FormGroup; - adminSettings: AdminSettings; + private adminSettings: AdminSettings; + + slackSettingsForm: FormGroup; + private notificationSettings: NotificationSettings; + + private readonly authUser: AuthUser; constructor(protected store: Store, private router: Router, private adminService: AdminService, + private notificationService: NotificationService, private dialog: MatDialog, public fb: FormBuilder) { super(store); - } - - ngOnInit() { + this.authUser = getCurrentAuthUser(this.store); this.buildSmsProviderForm(); - this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe( - (adminSettings) => { - this.adminSettings = adminSettings; - this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); - }, - () => { - this.adminSettings = { - key: 'sms', - jsonValue: null - }; - this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + this.buildGeneralServerSettingsForm(); + this.notificationService.getNotificationSettings().subscribe( + (settings) => { + this.notificationSettings = settings; + this.slackSettingsForm.reset(this.notificationSettings); } ); + if (this.isSysAdmin()) { + this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe({ + next: adminSettings => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + }, + error: () => { + this.adminSettings = { + key: 'sms', + jsonValue: null + }; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + }); + } } - buildSmsProviderForm() { + private buildSmsProviderForm() { this.smsProvider = this.fb.group({ configuration: [null, [Validators.required]] }); @@ -89,7 +108,42 @@ export class SmsProviderComponent extends PageComponent implements OnInit, HasCo } confirmForm(): FormGroup { - return this.smsProvider; + return this.smsProvider.dirty ? this.smsProvider : this.slackSettingsForm; + } + + private buildGeneralServerSettingsForm() { + this.slackSettingsForm = this.fb.group({ + deliveryMethodsConfigs: this.fb.group({ + SLACK: this.fb.group({ + botToken: [''] + }) + }) + }); + this.registerDisableOnLoadFormControl(this.slackSettingsForm.get('deliveryMethodsConfigs')); + } + + saveNotification(): void { + this.notificationSettings = deepTrim({ + ...this.notificationSettings, + ...this.slackSettingsForm.value + }); + // eslint-disable-next-line guard-for-in + for (const method in this.notificationSettings.deliveryMethodsConfigs) { + const keys = Object.keys(this.notificationSettings.deliveryMethodsConfigs[method]); + if (keys.some(item => isEmptyStr(this.notificationSettings.deliveryMethodsConfigs[method][item]))) { + delete this.notificationSettings.deliveryMethodsConfigs[method]; + } else { + this.notificationSettings.deliveryMethodsConfigs[method].method = method; + } + } + this.notificationService.saveNotificationSettings(this.notificationSettings).subscribe(setting => { + this.notificationSettings = setting; + this.slackSettingsForm.reset(this.notificationSettings); + }); + } + + isSysAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html index d658de9589..69cacf3604 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html @@ -1,6 +1,6 @@
    - - -
    - admin.2fa.2fa - -
    -
    -
    + + + + admin.2fa.2fa + + +
    +
    @@ -38,7 +38,7 @@ {{ twoFactorAuthProvidersData.get(provider.value.providerType).name | translate }} @@ -153,7 +153,7 @@ - {{ 'admin.2fa.verification-code-check-rate-limit' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss index 254cc51c22..fea3045098 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - @import "../../../../../scss/constants"; :host { - mat-card.settings-card { + .mat-mdc-card.settings-card { @media #{$mat-md} { width: 90%; } @@ -58,7 +57,7 @@ height: 48px; } - .mat-slide-toggle { + .mat-mdc-slide-toggle { margin-right: 8px; } } @@ -72,6 +71,13 @@ } :host ::ng-deep { + + .mat-mdc-form-field { + .mat-mdc-form-field-infix { + width: 100%; + } + } + .mat-expansion-panel { .mat-expansion-panel-content { font-size: 16px; diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts index 9e79d2d725..6bd630ae88 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { PageComponent } from '@shared/components/page.component'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service'; import { TwoFactorAuthProviderConfigForm, @@ -43,7 +43,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI private readonly destroy$ = new Subject(); private readonly posIntValidation = [Validators.required, Validators.min(1), Validators.pattern(/^\d*$/)]; - twoFaFormGroup: FormGroup; + twoFaFormGroup: UntypedFormGroup; twoFactorAuthProviderType = TwoFactorAuthProviderType; twoFactorAuthProvidersData = twoFactorAuthProvidersData; @@ -51,7 +51,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI constructor(protected store: Store, private twoFaService: TwoFactorAuthenticationService, - private fb: FormBuilder) { + private fb: UntypedFormBuilder) { super(store); } @@ -68,7 +68,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI this.destroy$.complete(); } - confirmForm(): FormGroup { + confirmForm(): UntypedFormGroup { return this.twoFaFormGroup; } @@ -99,9 +99,9 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI $event.stopPropagation(); } if (currentState) { - this.getByIndexPanel(index).close(); - } else { this.getByIndexPanel(index).open(); + } else { + this.getByIndexPanel(index).close(); } } @@ -109,8 +109,8 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI return item; } - get providersForm(): FormArray { - return this.twoFaFormGroup.get('providers') as FormArray; + get providersForm(): UntypedFormArray { + return this.twoFaFormGroup.get('providers') as UntypedFormArray; } private build2faSettingsForm(): void { diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts new file mode 100644 index 0000000000..30a6cbb8fd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts @@ -0,0 +1,58 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { Observable } from 'rxjs'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; +import { AlarmsMode } from '@shared/models/alarm.models'; + +@Injectable() +export class OAuth2LoginProcessingUrlResolver implements Resolve { + + constructor(private oauth2Service: OAuth2Service) { + } + + resolve(): Observable { + return this.oauth2Service.getLoginProcessingUrl(); + } +} + +const routes: Routes = [ + { + path: 'alarms', + component: AlarmTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'alarm.alarms', + breadcrumb: { + label: 'alarm.alarms', + icon: 'notifications' + }, + isPage: true, + alarmsMode: AlarmsMode.ALL + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [] +}) +export class AlarmRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts new file mode 100644 index 0000000000..cd6f257833 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; +import { AlarmRoutingModule } from '@home/pages/alarm/alarm-routing.module'; + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + HomeDialogsModule, + AlarmRoutingModule + ] +}) +export class AlarmModule { } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts index f1db71d466..a38fbf9958 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html index 6563b6d941..7c9cc1bba1 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html @@ -1,6 +1,6 @@ { diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html index 452dce8bcd..22ab3d855e 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html @@ -1,6 +1,6 @@ { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index a2862382e5..7ce78ec6ac 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -1,6 +1,6 @@ +
    + +

    info_outline + {{ 'edge.install-connect-instructions' | translate }}

    + + +
    + + +
    +
    + +
    +
    + +
    +
    diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts new file mode 100644 index 0000000000..a4dfe4345b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts @@ -0,0 +1,46 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; +import { DialogComponent } from "@shared/components/dialog.component"; +import { Store } from "@ngrx/store"; +import { AppState } from "@core/core.state"; +import { Router } from "@angular/router"; + +export interface EdgeInstructionsData { + instructions: string; +} + +@Component({ + selector: 'tb-edge-instructions', + templateUrl: './edge-instructions-dialog.component.html' +}) +export class EdgeInstructionsDialogComponent extends DialogComponent { + + instructions: string = this.data.instructions; + + constructor(protected store: Store, + protected router: Router, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: EdgeInstructionsData) { + super(store, router, dialogRef); + } + + cancel(): void { + this.dialogRef.close(null); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts index 297669002a..5dcdee3169 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -44,49 +44,28 @@ import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages const routes: Routes = [ { - path: 'edgeInstances', + path: 'edgeManagement', data: { breadcrumb: { - label: 'edge.edge-instances', - icon: 'router' + label: 'edge.management', + icon: 'settings_input_antenna' } }, children: [ { path: '', - component: EntitiesTableComponent, + children: [], data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.edge-instances', - edgesType: 'tenant' - }, - resolve: { - entitiesTableConfig: EdgesTableConfigResolver + redirectTo: '/edgeManagement/instances' } }, { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: 'instances', data: { breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, + label: 'edge.instances', icon: 'router' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.edge-instances', - edgesType: 'tenant' - }, - resolve: { - entitiesTableConfig: EdgesTableConfigResolver - } - }, - { - path: ':edgeId/assets', - data: { - breadcrumb: { - label: 'edge.assets', - icon: 'domain' } }, children: [ @@ -95,11 +74,11 @@ const routes: Routes = [ component: EntitiesTableComponent, data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.assets', - assetsType: 'edge' + title: 'edge.edge-instances', + edgesType: 'tenant' }, resolve: { - entitiesTableConfig: AssetsTableConfigResolver + entitiesTableConfig: EdgesTableConfigResolver } }, { @@ -109,197 +88,219 @@ const routes: Routes = [ data: { breadcrumb: { labelFunction: entityDetailsPageBreadcrumbLabelFunction, - icon: 'domain' + icon: 'router' } as BreadCrumbConfig, auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.assets', - assetsType: 'edge' - }, - resolve: { - entitiesTableConfig: AssetsTableConfigResolver - } - } - ] - }, - { - path: ':edgeId/devices', - data: { - breadcrumb: { - label: 'edge.devices', - icon: 'devices_other' - } - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.devices', - devicesType: 'edge' + title: 'edge.edge-instances', + edgesType: 'tenant' }, resolve: { - entitiesTableConfig: DevicesTableConfigResolver + entitiesTableConfig: EdgesTableConfigResolver } }, { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: ':edgeId/assets', data: { breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, - icon: 'devices_other' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.devices', - devicesType: 'edge' - }, - resolve: { - entitiesTableConfig: DevicesTableConfigResolver - } - } - ] - }, - { - path: ':edgeId/entityViews', - data: { - breadcrumb: { - label: 'edge.entity-views', - icon: 'view_quilt' - }, - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.entity-views', - entityViewsType: 'edge' + label: 'edge.assets', + icon: 'domain' + } }, - resolve: { - entitiesTableConfig: EntityViewsTableConfigResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.assets', + assetsType: 'edge' + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'domain' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.assets', + assetsType: 'edge' + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } + } + ] }, { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: ':edgeId/devices', data: { breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, + label: 'edge.devices', icon: 'devices_other' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.entity-views', - entityViewsType: 'edge' + } }, - resolve: { - entitiesTableConfig: EntityViewsTableConfigResolver - } - } - ] - }, - { - path: ':edgeId/dashboards', - data: { - breadcrumb: { - label: 'edge.dashboards', - icon: 'dashboard' - } - }, - children: [ + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.devices', + devicesType: 'edge' + }, + resolve: { + entitiesTableConfig: DevicesTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'devices_other' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.devices', + devicesType: 'edge' + }, + resolve: { + entitiesTableConfig: DevicesTableConfigResolver + } + } + ] + }, { - path: '', - component: EntitiesTableComponent, + path: ':edgeId/entityViews', data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - dashboardsType: 'edge' - }, - resolve: { - entitiesTableConfig: DashboardsTableConfigResolver + breadcrumb: { + label: 'edge.entity-views', + icon: 'view_quilt' + }, }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.entity-views', + entityViewsType: 'edge' + }, + resolve: { + entitiesTableConfig: EntityViewsTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'devices_other' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.entity-views', + entityViewsType: 'edge' + }, + resolve: { + entitiesTableConfig: EntityViewsTableConfigResolver + } + } + ] }, { - path: ':dashboardId', - component: DashboardPageComponent, + path: ':edgeId/dashboards', data: { breadcrumb: { - labelFunction: dashboardBreadcumbLabelFunction, + label: 'edge.dashboards', icon: 'dashboard' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.dashboard', - widgetEditMode: false - }, - resolve: { - dashboard: DashboardResolver - } - } - ] - }, - { - path: ':edgeId/ruleChains', - data: { - breadcrumb: { - label: 'edge.edge-rulechains', - icon: 'settings_ethernet' - } - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN], - title: 'edge.rulechains', - ruleChainsType: 'edge' + } }, - resolve: { - entitiesTableConfig: RuleChainsTableConfigResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + dashboardsType: 'edge' + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + }, + }, + { + path: ':dashboardId', + component: DashboardPageComponent, + data: { + breadcrumb: { + labelFunction: dashboardBreadcumbLabelFunction, + icon: 'dashboard' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.dashboard', + widgetEditMode: false + }, + resolve: { + dashboard: DashboardResolver + } + } + ] }, { - path: ':ruleChainId', - component: RuleChainPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: ':edgeId/ruleChains', data: { breadcrumb: { - labelFunction: ruleChainBreadcumbLabelFunction, + label: 'edge.edge-rulechains', icon: 'settings_ethernet' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN], - title: 'rulechain.edge-rulechain', - import: false, - ruleChainType: RuleChainType.EDGE + } }, - resolve: { - ruleChain: RuleChainResolver, - ruleChainMetaData: RuleChainMetaDataResolver, - ruleNodeComponents: RuleNodeComponentsResolver, - tooltipster: TooltipsterResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'edge.rulechains', + ruleChainsType: 'edge' + }, + resolve: { + entitiesTableConfig: RuleChainsTableConfigResolver + } + }, + { + path: ':ruleChainId', + component: RuleChainPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: ruleChainBreadcumbLabelFunction, + icon: 'settings_ethernet' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'rulechain.edge-rulechain', + import: false, + ruleChainType: RuleChainType.EDGE + }, + resolve: { + ruleChain: RuleChainResolver, + ruleChainMetaData: RuleChainMetaDataResolver, + ruleNodeComponents: RuleNodeComponentsResolver, + tooltipster: TooltipsterResolver + } + } + ] } ] - } - ] - }, - { - path: 'edgeManagement', - data: { - breadcrumb: { - label: 'edge.management', - icon: 'settings_input_antenna' - } - }, - children: [ - { - path: '', - data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - redirectTo: '/edgeManagement/ruleChains' - } }, { path: 'ruleChains', @@ -366,7 +367,63 @@ const routes: Routes = [ ] } ] - }]; + }, + { + path: 'edgeInstances', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances' + }, + { + path: 'edgeInstances/:entityId', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:entityId' + }, + { + path: 'edgeInstances/:edgeId/assets', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/assets' + }, + { + path: 'edgeInstances/:edgeId/assets/:entityId', + redirectTo: '/edgeManagement/instances/:edgeId/assets/:entityId' + }, + { + path: 'edgeInstances/:edgeId/devices', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/devices' + }, + { + path: 'edgeInstances/:edgeId/devices/:entityId', + redirectTo: '/edgeManagement/instances/:edgeId/devices/:entityId' + }, + { + path: 'edgeInstances/:edgeId/entityViews', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/entityViews' + }, + { + path: 'edgeInstances/:edgeId/entityViews/:entityId', + redirectTo: '/edgeManagement/instances/:edgeId/entityViews/:entityId' + }, + { + path: 'edgeInstances/:edgeId/dashboards', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/dashboards' + }, + { + path: 'edgeInstances/:edgeId/dashboards/:dashboardId', + redirectTo: '/edgeManagement/instances/:edgeId/dashboards/:dashboardId' + }, + { + path: 'edgeInstances/:edgeId/ruleChains', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/ruleChains' + }, + { + path: 'edgeInstances/:edgeId/ruleChains/:ruleChainId', + redirectTo: '/edgeManagement/instances/:edgeId/ruleChains/:ruleChainId' + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.html b/ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.html index 2b2d8a1e4c..8a7338a8ff 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.html +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.html @@ -1,6 +1,6 @@ { diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-tabs.component.html b/ui-ngx/src/app/modules/home/pages/edge/edge-tabs.component.html index 99a8cd7ebb..caaf368f3d 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-tabs.component.html @@ -1,6 +1,6 @@ { diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html index 79307ddcb2..87ee70f1a1 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html @@ -1,6 +1,6 @@ + diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-select.component.scss b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.scss similarity index 81% rename from ui-ngx/src/app/modules/home/components/filter/filter-select.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.scss index 6132c393a9..222482999d 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-select.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { - -} - -:host ::ng-deep { - .mat-form-field-infix { - border-top: none; - } +:host{ + display: block; + max-width: 400px; + min-width: 350px; + padding: 2px; } diff --git a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts new file mode 100644 index 0000000000..275caa6647 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Notification } from '@shared/models/notification.models'; + +export interface InboxNotificationDialogData { + notification: Notification; +} + +@Component({ + selector: 'tb-inbox-notification-dialog', + templateUrl: './inbox-notification-dialog.component.html', + styleUrls: ['inbox-notification-dialog.component.scss'] +}) +export class InboxNotificationDialogComponent extends DialogComponent { + + notification: Notification; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: InboxNotificationDialogData) { + super(store, router, dialogRef); + + this.notification = data.notification; + } + + markAsRead(id: string) { + this.dialogRef.close(id); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts new file mode 100644 index 0000000000..f8088c28cb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts @@ -0,0 +1,165 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + CellActionDescriptor, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { DatePipe } from '@angular/common'; +import { Direction } from '@shared/models/page/sort-order'; +import { + Notification, + NotificationStatus, + NotificationTemplateTypeTranslateMap +} from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { InboxTableHeaderComponent } from '@home/pages/notification/inbox/inbox-table-header.component'; +import { TranslateService } from '@ngx-translate/core'; +import { take } from 'rxjs/operators'; +import { MatDialog } from '@angular/material/dialog'; +import { + InboxNotificationDialogComponent, + InboxNotificationDialogData +} from '@home/pages/notification/inbox/inbox-notification-dialog.component'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; + +@Injectable() +export class InboxTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.NOTIFICATION; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.rowPointer = true; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION); + this.config.entityResources = {} as EntityTypeResource; + + this.config.deleteEntityTitle = () => this.translate.instant('notification.delete-notification-title'); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-notification-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-notifications-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-notifications-text'); + + this.config.deleteEntity = id => this.notificationService.deleteNotification(id.id); + this.config.entitiesFetchFunction = pageLink => + this.notificationService.getNotifications(pageLink, this.config.componentsData.unreadOnly); + + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.config.handleRowClick = ($event, notification) => { + this.showNotification($event, notification); + return true; + }; + + this.config.componentsData = { + unreadOnly: true + }; + + this.config.cellActionDescriptors = this.configureCellActions(); + + this.config.headerComponent = InboxTableHeaderComponent; + + this.config.headerActionDescriptors = [{ + name: this.translate.instant('notification.mark-all-as-read'), + icon: 'done_all', + isEnabled: () => true, + onAction: $event => this.markAllRead($event) + }]; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('type', 'notification.type', '10%', (notification) => + this.translate.instant(NotificationTemplateTypeTranslateMap.get(notification.type).name)), + new EntityTableColumn('subject', 'notification.subject', '30%'), + new EntityTableColumn('text', 'notification.message', '60%') + ); + + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private configureCellActions(): Array> { + return [{ + name: this.translate.instant('notification.mark-as-read'), + icon: 'check_circle_outline', + isEnabled: (notification) => notification.status !== NotificationStatus.READ, + onAction: ($event, entity) => this.markAsRead($event, entity) + }]; + } + + private markAllRead($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.notificationService.markAllNotificationsAsRead().subscribe(() => { + if (this.config.componentsData.unreadOnly) { + this.config.getTable().resetSortAndFilter(true); + } else { + this.config.updateData(); + } + }); + } + + private markAsRead($event, entity){ + if ($event) { + $event.stopPropagation(); + } + this.notificationService.markNotificationAsRead(entity.id.id).subscribe(() => { + if (this.config.componentsData.unreadOnly) { + this.config.getTable().dataSource.pageData$.pipe(take(1)).subscribe( + (value) => { + if (value.data.length === 1 && this.config.getTable().pageLink.page) { + this.config.getTable().pageLink.page--; + } + this.config.updateData(); + } + ); + } else { + entity.status = NotificationStatus.READ; + this.config.getTable().detectChanges(); + } + }); + } + + private showNotification($event: Event, notification: Notification) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(InboxNotificationDialogComponent, { + disableClose: false, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + notification + } + }).afterClosed().subscribe(res => { + if (res) { + this.markAsRead(null, notification); + } + }); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.html b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.html new file mode 100644 index 0000000000..be19b8beaf --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.html @@ -0,0 +1,27 @@ + +
    + + {{ 'notification.unread' | translate }} + {{ 'notification.all' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.scss new file mode 100644 index 0000000000..01519235aa --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.scss @@ -0,0 +1,72 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../theme"; + +:host ::ng-deep { + .mat-button-toggle-group.tb-notification-unread-toggle-group { + &.mat-button-toggle-group-appearance-standard { + border: none; + border-radius: 14px; + + .mat-button-toggle + .mat-button-toggle { + border-left: none; + } + } + + .mat-button-toggle { + background: rgba(0, 0, 0, 0.06); + height: 28px; + align-items: center; + display: flex; + + .mat-button-toggle-ripple { + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border-radius: 14px; + } + } + + .mat-button-toggle-button { + color: #959595; + } + + .mat-button-toggle-focus-overlay { + border-radius: 14px; + margin: 2px; + } + + .mat-button-toggle-checked .mat-button-toggle-button { + background-color: $tb-primary-color; + color: #fff; + border-radius: 14px; + margin-left: 2px; + margin-right: 2px; + } + + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + line-height: 24px; + font-size: 14px; + font-weight: 500; + letter-spacing: .25px; + } + + .mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { + opacity: .01; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.ts new file mode 100644 index 0000000000..120d22c15e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Notification } from '@shared/models/notification.models'; + +@Component({ + selector: 'tb-inbox-table-header', + templateUrl: './inbox-table-header.component.html', + styleUrls: ['inbox-table-header.component.scss'] +}) +export class InboxTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } + + changeUnreadOnly(unreadOnly: boolean) { + this.entitiesTableConfig.componentsData.unreadOnly = unreadOnly; + this.entitiesTableConfig.getTable().resetSortAndFilter(true); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts b/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts new file mode 100644 index 0000000000..480cf97f99 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts @@ -0,0 +1,140 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { NgModule } from '@angular/core'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { InboxTableConfigResolver } from '@home/pages/notification/inbox/inbox-table-config.resolver'; +import { SentTableConfigResolver } from '@home/pages/notification/sent/sent-table-config.resolver'; +import { RecipientTableConfigResolver } from '@home/pages/notification/recipient/recipient-table-config.resolver'; +import { TemplateTableConfigResolver } from '@home/pages/notification/template/template-table-config.resolver'; +import { RuleTableConfigResolver } from '@home/pages/notification/rule/rule-table-config.resolver'; +import { SendNotificationButtonComponent } from '@home/components/notification/send-notification-button.component'; + +const routes: Routes = [ + { + path: 'notification', + component: RouterTabsComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + breadcrumb: { + label: 'notification.notification-center', + icon: 'mdi:message-badge' + }, + routerTabsHeaderComponent: SendNotificationButtonComponent + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + redirectTo: '/notification/inbox' + } + }, + { + path: 'inbox', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + title: 'notification.inbox', + breadcrumb: { + label: 'notification.inbox', + icon: 'inbox' + } + }, + resolve: { + entitiesTableConfig: InboxTableConfigResolver + } + }, + { + path: 'sent', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.sent', + breadcrumb: { + label: 'notification.sent', + icon: 'outbox' + } + }, + resolve: { + entitiesTableConfig: SentTableConfigResolver + } + }, + { + path: 'templates', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.templates', + breadcrumb: { + label: 'notification.templates', + icon: 'mdi:message-draw' + } + }, + resolve: { + entitiesTableConfig: TemplateTableConfigResolver + } + }, + { + path: 'recipients', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.recipients', + breadcrumb: { + label: 'notification.recipients', + icon: 'contacts' + }, + }, + resolve: { + entitiesTableConfig: RecipientTableConfigResolver + } + }, + { + path: 'rules', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.rules', + breadcrumb: { + label: 'notification.rules', + icon: 'mdi:message-cog' + } + }, + resolve: { + entitiesTableConfig: RuleTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + providers: [ + InboxTableConfigResolver, + SentTableConfigResolver, + RecipientTableConfigResolver, + TemplateTableConfigResolver, + RuleTableConfigResolver + ], + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class NotificationRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/notification/notification.module.ts b/ui-ngx/src/app/modules/home/pages/notification/notification.module.ts new file mode 100644 index 0000000000..5ab7bfa957 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/notification.module.ts @@ -0,0 +1,61 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { NotificationRoutingModule } from '@home/pages/notification/notification-routing.module'; +import { InboxTableHeaderComponent } from '@home/pages/notification/inbox/inbox-table-header.component'; +import { InboxNotificationDialogComponent } from '@home/pages/notification/inbox/inbox-notification-dialog.component'; +import { HomeComponentsModule } from '@home/components/home-components.module'; +import { SentErrorDialogComponent } from '@home/pages/notification/sent/sent-error-dialog.component'; +import { SentNotificationDialogComponent } from '@home/pages/notification/sent/sent-notification-dialog.componet'; +import { + RecipientNotificationDialogComponent +} from '@home/pages/notification/recipient/recipient-notification-dialog.component'; +import { RecipientTableHeaderComponent } from '@home/pages/notification/recipient/recipient-table-header.component'; +import { + TemplateNotificationDialogComponent +} from '@home/pages/notification/template/template-notification-dialog.component'; +import { TemplateTableHeaderComponent } from '@home/pages/notification/template/template-table-header.component'; +import { EscalationFormComponent } from '@home/pages/notification/rule/escalation-form.component'; +import { EscalationsComponent } from '@home/pages/notification/rule/escalations.component'; +import { RuleNotificationDialogComponent } from '@home/pages/notification/rule/rule-notification-dialog.component'; +import { RuleTableHeaderComponent } from '@home/pages/notification/rule/rule-table-header.component'; + +@NgModule({ + declarations: [ + InboxTableHeaderComponent, + InboxNotificationDialogComponent, + SentErrorDialogComponent, + SentNotificationDialogComponent, + RecipientNotificationDialogComponent, + RecipientTableHeaderComponent, + TemplateNotificationDialogComponent, + TemplateTableHeaderComponent, + EscalationFormComponent, + EscalationsComponent, + RuleNotificationDialogComponent, + RuleTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + NotificationRoutingModule, + HomeComponentsModule + ] +}) +export class NotificationModule { } diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html new file mode 100644 index 0000000000..9eecacf268 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html @@ -0,0 +1,139 @@ + +
    + +

    {{ (isAdd ? 'notification.add-notification-recipients-group' : 'notification.edit-notification-recipients-group') | translate }}

    + + +
    + + +
    +
    + + notification.name + + + {{ 'notification.name-required' | translate }} + + +
    + + + +
    +
    {{ notificationTargetTypeTranslationMap.get(notificationTargetType) | translate }}
    +
    +
    +
    +
    + + notification.recipient-type.user-filters + + + {{ notificationTargetConfigTypeInfoMap.get(type).name | translate }} + + + {{ notificationTargetConfigTypeInfoMap.get(targetNotificationForm.get('configuration.usersFilter.type').value).hint | translate }} + + + +
    +
    + + {{ 'tenant.tenant' | translate }} + {{ 'tenant-profile.tenant-profile' | translate }} + +
    + + + + + + + + +
    +
    + + + + + + + + +
    +
    +
    + + + + {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }} + + + + +
    + + notification.description + + +
    +
    + +
    + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.scss new file mode 100644 index 0000000000..8406cae3fe --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.scss @@ -0,0 +1,109 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../scss/constants"; +@import "../../../../../../theme"; + +:host { + form.tb-dialog-container { + min-width: 600px; + max-height: 100vh; + } + + .mat-dialog-content { + height: 85%; + } + + .tb-title { + display: block; + padding-bottom: 6px; + } +} + +:host ::ng-deep { + .mat-mdc-radio-group { + display: flex; + flex-direction: row; + margin-bottom: 22px; + gap: 12px; + + .mat-mdc-radio-button { + flex: 1 1 100%; + padding: 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; + } + } + + @media #{$mat-xs} { + .mat-mdc-radio-group { + flex-direction: column; + } + } + + .mat-button-toggle-group.tb-notification-tenant-group { + &.mat-button-toggle-group-appearance-standard { + border: none; + border-radius: 18px; + margin-bottom: 14px; + + .mat-button-toggle + .mat-button-toggle { + border-left: none; + } + } + + .mat-button-toggle { + background: rgba(0, 0, 0, 0.06); + height: 36px; + align-items: center; + display: flex; + + .mat-button-toggle-ripple { + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border-radius: 18px; + } + } + + .mat-button-toggle-button { + color: #959595; + } + + .mat-button-toggle-focus-overlay { + border-radius: 18px; + margin: 2px; + } + + .mat-button-toggle-checked .mat-button-toggle-button { + background-color: $tb-primary-color; + color: #fff; + border-radius: 18px; + margin-left: 2px; + margin-right: 2px; + } + + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + line-height: 34px; + font-size: 16px; + font-weight: 500; + } + + .mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { + opacity: .01; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts new file mode 100644 index 0000000000..c07d8beb75 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts @@ -0,0 +1,210 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + NotificationTarget, + NotificationTargetConfigType, + NotificationTargetConfigTypeInfoMap, + NotificationTargetType, + NotificationTargetTypeTranslationMap, + SlackChanelType, + SlackChanelTypesTranslateMap +} from '@shared/models/notification.models'; +import { Component, Inject, OnDestroy } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { NotificationService } from '@core/http/notification.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { deepTrim, isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Authority } from '@shared/models/authority.enum'; +import { AuthState } from '@core/auth/auth.models'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; +import { AuthUser } from '@shared/models/user.model'; +import { control } from 'leaflet'; + +export interface RecipientNotificationDialogData { + target?: NotificationTarget; + isAdd?: boolean; +} + +@Component({ + selector: 'tb-target-notification-dialog', + templateUrl: './recipient-notification-dialog.component.html', + styleUrls: ['recipient-notification-dialog.component.scss'] +}) +export class RecipientNotificationDialogComponent extends + DialogComponent implements OnDestroy { + + private authState: AuthState = getCurrentAuthState(this.store); + private authUser: AuthUser = this.authState.authUser; + + targetNotificationForm: FormGroup; + notificationTargetType = NotificationTargetType; + notificationTargetTypes: NotificationTargetType[] = Object.values(NotificationTargetType); + notificationTargetTypeTranslationMap = NotificationTargetTypeTranslationMap; + notificationTargetConfigType = NotificationTargetConfigType; + notificationTargetConfigTypes: NotificationTargetConfigType[] = this.allowNotificationTargetConfigTypes(); + notificationTargetConfigTypeInfoMap = NotificationTargetConfigTypeInfoMap; + slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[]; + slackChanelTypesTranslateMap = SlackChanelTypesTranslateMap; + + entityType = EntityType; + isAdd = true; + + private readonly destroy$ = new Subject(); + private userFilterFormControls: string[]; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: RecipientNotificationDialogData, + private fb: FormBuilder, + private notificationService: NotificationService) { + super(store, router, dialogRef); + + if (isDefinedAndNotNull(data.isAdd)) { + this.isAdd = data.isAdd; + } + + this.targetNotificationForm = this.fb.group({ + name: [null, Validators.required], + configuration: this.fb.group({ + type: [NotificationTargetType.PLATFORM_USERS], + usersFilter: this.fb.group({ + type: [NotificationTargetConfigType.ALL_USERS], + filterByTenants: [{value: true, disabled: true}], + tenantsIds: [{value: null, disabled: true}], + tenantProfilesIds: [{value: null, disabled: true}], + customerId: [{value: null, disabled: true}, Validators.required], + usersIds: [{value: null, disabled: true}, Validators.required], + }), + conversationType: [{value: SlackChanelType.PUBLIC_CHANNEL, disabled: true}], + conversation: [{value: '', disabled: true}, Validators.required], + description: [null] + }) + }); + + this.targetNotificationForm.get('configuration.type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((type: NotificationTargetType) => { + this.targetNotificationForm.get('configuration').disable({emitEvent: false}); + switch (type) { + case NotificationTargetType.PLATFORM_USERS: + this.targetNotificationForm.get('configuration.usersFilter').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.usersFilter.type').updateValueAndValidity({onlySelf: true}); + break; + case NotificationTargetType.SLACK: + this.targetNotificationForm.get('configuration.conversationType').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.conversation').enable({emitEvent: false}); + break; + } + this.targetNotificationForm.get('configuration.type').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.description').enable({emitEvent: false}); + }); + + this.userFilterFormControls = Object.keys((this.targetNotificationForm.get('configuration.usersFilter') as FormGroup).controls) + .filter((controlName) => controlName !== 'type'); + + this.targetNotificationForm.get('configuration.usersFilter.type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((type: NotificationTargetConfigType) => { + this.userFilterFormControls.forEach( + controlName => this.targetNotificationForm.get(`configuration.usersFilter.${controlName}`).disable({emitEvent: false}) + ); + switch (type) { + case NotificationTargetConfigType.TENANT_ADMINISTRATORS: + if (this.isSysAdmin()) { + this.targetNotificationForm.get('configuration.usersFilter.filterByTenants').enable({onlySelf: true}); + } + break; + case NotificationTargetConfigType.USER_LIST: + this.targetNotificationForm.get('configuration.usersFilter.usersIds').enable({emitEvent: false}); + break; + case NotificationTargetConfigType.CUSTOMER_USERS: + this.targetNotificationForm.get('configuration.usersFilter.customerId').enable({emitEvent: false}); + break; + } + }); + + this.targetNotificationForm.get('configuration.usersFilter.filterByTenants').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: boolean) => { + if (value) { + this.targetNotificationForm.get('configuration.usersFilter.tenantsIds').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.usersFilter.tenantProfilesIds').disable({emitEvent: false}); + } else { + this.targetNotificationForm.get('configuration.usersFilter.tenantsIds').disable({emitEvent: false}); + this.targetNotificationForm.get('configuration.usersFilter.tenantProfilesIds').enable({emitEvent: false}); + } + }); + + if (isDefinedAndNotNull(data.target)) { + this.targetNotificationForm.patchValue(data.target, {emitEvent: false}); + this.targetNotificationForm.get('configuration.type').updateValueAndValidity({onlySelf: true}); + if (this.isSysAdmin() && data.target.configuration.usersFilter.type === NotificationTargetConfigType.TENANT_ADMINISTRATORS) { + this.targetNotificationForm.get('configuration.usersFilter.filterByTenants') + .patchValue(!Array.isArray(this.data.target.configuration.usersFilter.tenantProfilesIds), {onlySelf: true}); + } + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + save() { + let formValue = deepTrim(this.targetNotificationForm.value); + if (isDefinedAndNotNull(this.data.target)) { + formValue = Object.assign({}, this.data.target, formValue); + } + if (this.isSysAdmin() && formValue.configuration.type === NotificationTargetType.PLATFORM_USERS && + formValue.configuration.usersFilter.type === NotificationTargetConfigType.TENANT_ADMINISTRATORS) { + delete formValue.configuration.usersFilter.filterByTenants; + } + this.notificationService.saveNotificationTarget(formValue).subscribe( + (target) => this.dialogRef.close(target) + ); + } + + isSysAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; + } + + private allowNotificationTargetConfigTypes(): NotificationTargetConfigType[] { + if (this.isSysAdmin()) { + return [ + NotificationTargetConfigType.ALL_USERS, + NotificationTargetConfigType.TENANT_ADMINISTRATORS, + NotificationTargetConfigType.AFFECTED_TENANT_ADMINISTRATORS, + NotificationTargetConfigType.SYSTEM_ADMINISTRATORS + ]; + } + return Object.values(NotificationTargetConfigType).filter(type => + type !== NotificationTargetConfigType.AFFECTED_TENANT_ADMINISTRATORS && type !== NotificationTargetConfigType.SYSTEM_ADMINISTRATORS); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts new file mode 100644 index 0000000000..05259e1edb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts @@ -0,0 +1,126 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + CellActionDescriptor, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { NotificationTarget, NotificationTargetTypeTranslationMap } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { + RecipientNotificationDialogComponent, + RecipientNotificationDialogData +} from '@home/pages/notification/recipient/recipient-notification-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { RecipientTableHeaderComponent } from '@home/pages/notification/recipient/recipient-table-header.component'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { DatePipe } from '@angular/common'; + +@Injectable() +export class RecipientTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.NOTIFICATION_TARGET; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.rowPointer = true; + + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION_TARGET); + this.config.entityResources = {} as EntityTypeResource; + + this.config.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTargets(pageLink); + + this.config.deleteEntityTitle = target => this.translate.instant('notification.delete-recipient-title', {recipientName: target.name}); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-recipient-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-recipients-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-recipients-text'); + + this.config.deleteEntity = id => this.notificationService.deleteNotificationTarget(id.id); + + this.config.cellActionDescriptors = this.configureCellActions(); + + this.config.headerComponent = RecipientTableHeaderComponent; + this.config.onEntityAction = action => this.onTargetAction(action); + + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.config.handleRowClick = ($event, target) => { + this.editTarget($event, target); + return true; + }; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('name', 'notification.recipient-group', '20%'), + new EntityTableColumn('configuration.type', 'notification.type', '20%', + (target) => this.translate.instant(NotificationTargetTypeTranslationMap.get(target.configuration.type)), + () => ({}), false), + new EntityTableColumn('configuration.description', 'notification.description', '60%', + (target) => target.configuration.description || '', + () => ({}), false) + ); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private configureCellActions(): Array> { + return []; + } + + private editTarget($event: Event, target: NotificationTarget, isAdd = false) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(RecipientNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + target + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.updateData(); + } + }); + } + + private onTargetAction(action: EntityAction): boolean { + switch (action.action) { + case 'add': + this.editTarget(action.event, action.entity, true); + return true; + } + return false; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html new file mode 100644 index 0000000000..e2effa3207 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html @@ -0,0 +1,24 @@ + +
    + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.scss similarity index 82% rename from ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.scss index 1e64b91940..33537b4b23 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - .mat-checkbox-label { - white-space: normal; - } +:host{ + width: 10000px; } diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts new file mode 100644 index 0000000000..f7285d3836 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { NotificationTarget } from '@shared/models/notification.models'; + +@Component({ + selector: 'tb-recipient-table-header', + templateUrl: './recipient-table-header.component.html', + styleUrls: ['recipient-table-header.component.scss'] +}) +export class RecipientTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } + + createTarget($event: Event) { + this.entitiesTableConfig.onEntityAction({event: $event, action: 'add', entity: null}); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html new file mode 100644 index 0000000000..b9a5ef2119 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html @@ -0,0 +1,48 @@ + +
    +
    +
    notification.first-recipient
    + +
    + notification.after + + notification.notify +
    +
    + + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.scss b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.scss new file mode 100644 index 0000000000..52ff558723 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.scss @@ -0,0 +1,66 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../scss/constants"; + +:host { + .escalation { + height: 100%; + min-height: 74px; + max-height: 100%; + border-radius: 8px; + background: rgba(0,0,0,0.04); + padding: 0 10px; + + > div:first-child { + padding-right: 10px; + } + } + + .escalation-notify { + text-align: center; + } + + tb-timeinterval { + min-width: 100px; + padding: 0 10px; + } + + @media #{$mat-xs} { + .escalation { + > div:first-child { + padding: 10px 0 0; + } + } + + tb-timeinterval { + padding: 0; + } + } +} + +:host ::ng-deep { + .mat-mdc-form-field { + min-width: 100px; + + .mat-mdc-form-field-infix { + width: 100%; + } + + .mat-mdc-select { + min-width: 110px !important; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.ts new file mode 100644 index 0000000000..089b234a24 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.ts @@ -0,0 +1,173 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { + NonConfirmedNotificationEscalation, + NotificationTarget, + NotificationType +} from '@shared/models/notification.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { takeUntil } from 'rxjs/operators'; +import { + RecipientNotificationDialogComponent, + RecipientNotificationDialogData +} from '@home/pages/notification/recipient/recipient-notification-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { MatButton } from '@angular/material/button'; + +@Component({ + selector: 'tb-escalation-form', + templateUrl: './escalation-form.component.html', + styleUrls: ['./escalation-form.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EscalationFormComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => EscalationFormComponent), + multi: true, + } + ] +}) +export class EscalationFormComponent implements ControlValueAccessor, OnInit, OnDestroy, Validator { + + @Input() + disabled: boolean; + + @Input() + systemEscalation = false; + + escalationFormGroup: FormGroup; + + entityType = EntityType; + notificationType = NotificationType; + + private modelValue; + private propagateChange = null; + private propagateChangePending = false; + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder, + private dialog: MatDialog) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + if (this.propagateChangePending) { + this.propagateChangePending = false; + setTimeout(() => { + this.propagateChange(this.modelValue); + }, 0); + } + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.escalationFormGroup = this.fb.group( + { + delayInSec: [0], + targets: [null, Validators.required], + }); + this.escalationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.escalationFormGroup.disable({emitEvent: false}); + } else { + this.escalationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: NonConfirmedNotificationEscalation): void { + this.propagateChangePending = false; + this.modelValue = value; + this.modelValue.delayInSec = +value.delayInSec; + if (isDefinedAndNotNull(this.modelValue)) { + this.escalationFormGroup.patchValue(this.modelValue, {emitEvent: false}); + } + if (!this.disabled && !this.escalationFormGroup.valid) { + this.updateModel(); + } + } + + createTarget($event: Event, button: MatButton) { + if ($event) { + $event.stopPropagation(); + } + button._elementRef.nativeElement.blur(); + this.dialog.open(RecipientNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: {} + }).afterClosed() + .subscribe((res) => { + if (res) { + let formValue: string[] = this.escalationFormGroup.get('targets').value; + if (!formValue) { + formValue = []; + } + formValue.push(res.id.id); + this.escalationFormGroup.get('targets').patchValue(formValue); + } + }); + } + + public validate(c: FormControl) { + return (this.escalationFormGroup.valid) ? null : { + escalation: { + valid: false, + }, + }; + } + + private updateModel() { + const value = this.escalationFormGroup.value; + this.modelValue = {...this.modelValue, ...value}; + if (this.propagateChange) { + this.propagateChange(this.modelValue); + } else { + this.propagateChangePending = true; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.html new file mode 100644 index 0000000000..1d7041ee43 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.html @@ -0,0 +1,48 @@ + +
    +
    + + + + +
    +
    + notification.no-rule +
    +
    + +
    +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.ts new file mode 100644 index 0000000000..fb102b4243 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.ts @@ -0,0 +1,160 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { Subject } from 'rxjs'; +import { NonConfirmedNotificationEscalation } from '@shared/models/notification.models'; +import { takeUntil } from 'rxjs/operators'; +import { coerceBoolean } from '@shared/decorators/coerce-boolean'; + +@Component({ + selector: 'tb-escalations-component', + templateUrl: './escalations.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EscalationsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => EscalationsComponent), + multi: true, + } + ] +}) +export class EscalationsComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { + + escalationsFormGroup: FormGroup; + + @Input() + @coerceBoolean() + required: boolean; + + @Input() + disabled: boolean; + + private destroy$ = new Subject(); + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.escalationsFormGroup = this.fb.group({ + escalations: this.fb.array([]) + }); + + this.escalationsFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); + } + + get escalationsFormArray(): FormArray { + return this.escalationsFormGroup.get('escalations') as FormArray; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.escalationsFormGroup.disable({emitEvent: false}); + } else { + this.escalationsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(escalations: {[key: string]: Array} | null): void { + const escalationParse: Array = []; + // eslint-disable-next-line guard-for-in + for (const escalation in escalations) { + escalationParse.push({delayInSec: Number(escalation) * 1000, targets: escalations[escalation]}); + } + if (escalationParse.length === 0) { + this.addEscalation(0); + } else if (escalationParse?.length === this.escalationsFormArray.length) { + this.escalationsFormArray.patchValue(escalationParse, {emitEvent: false}); + } else { + const escalationsControls: Array = []; + escalationParse.forEach(escalation => { + escalationsControls.push(this.fb.control(escalation, [Validators.required])); + }); + this.escalationsFormGroup.setControl('escalations', this.fb.array(escalationsControls), {emitEvent: false}); + if (this.disabled) { + this.escalationsFormGroup.disable({emitEvent: false}); + } else { + this.escalationsFormGroup.enable({emitEvent: false}); + } + } + } + + public removeEscalation(index: number) { + (this.escalationsFormGroup.get('escalations') as FormArray).removeAt(index); + } + + public addEscalation(delay = 3600000) { + const escalation = { + delayInSec: delay, + targets: null + }; + const escalationArray = this.escalationsFormGroup.get('escalations') as FormArray; + escalationArray.push(this.fb.control(escalation, []), {emitEvent: false}); + } + + public validate(c: AbstractControl): ValidationErrors | null { + return this.escalationsFormGroup.valid ? null : { + escalation: { + valid: false, + }, + }; + } + + private updateModel() { + const escalations = {}; + this.escalationsFormGroup.get('escalations').value.forEach( + escalation => escalations[escalation.delayInSec / 1000] = escalation.targets + ); + this.propagateChange(escalations); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html new file mode 100644 index 0000000000..a3e7d55b60 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -0,0 +1,491 @@ + + +

    {{ dialogTitle | translate }}

    + + +
    + + +
    +
    + + + check + + + {{ 'notification.basic-settings' | translate }} +
    + + notification.rule-name + + + {{ 'notification.rule-name-required' | translate }} + + + + notification.trigger.trigger + + + {{ triggerTypeTranslationMap.get(trigger) | translate }} + + + + {{ 'notification.trigger.trigger-required' | translate }} + + + + +
    + + + +
    + +
    + notification.escalation-chain +
    notification.recipients
    + + +
    +
    +
    + +
    +
    +
    +
    notification.stop-escalation-alarm-status-become
    + + alarm.alarm-status-list + + + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} + + + +
    +
    +
    +
    +
    + + {{ 'notification.alarm-trigger-settings' | translate }} +
    +
    +
    + notification.filter + + + + + alarm.alarm-severity-list + + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + + +
    + + notification.notify-on + + + {{ alarmActionTranslationMap.get(alarmAction) | translate }} + + + + {{ 'notification.notify-on-required' | translate }} + + +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + + {{ 'notification.device-activity-trigger-settings' | translate }} +
    +
    +
    + + {{ 'device.device' | translate }} + {{ 'device-profile.device-profile' | translate }} + +
    + + + + + + + + + + notification.notify-on + + + {{ deviceEventTranslationMap.get(deviceEvent) | translate }} + + + + {{ 'notification.notify-on-required' | translate }} + + +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + + {{ 'notification.entity-action-trigger-settings' | translate }} +
    +
    + notification.filter + + +
    + notification.status + {{ 'notification.created' | translate }} + {{ 'notification.updated' | translate }} + {{ 'notification.deleted' | translate }} +
    +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + + {{ 'notification.alarm-comment-trigger-settings' | translate }} +
    +
    +
    + notification.filter + + + + + alarm.alarm-severity-list + + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + + + + alarm.alarm-status-list + + + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} + + + +
    +
    + + {{ 'notification.notify-only-user-comments' | translate }} + + + {{ 'notification.notify-on-comment-update' | translate }} + +
    +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + {{ 'notification.alarm-assignment-trigger-settings' | translate }} +
    +
    +
    + notification.filter + + + + + alarm.alarm-severity-list + + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + + + + alarm.alarm-status-list + + + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} + + + +
    + + notification.notify-on + + + {{ alarmAssignmentActionTranslationMap.get(alarmAssignmentAction) | translate }} + + + + {{ 'notification.notify-on-required' | translate }} + + +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + {{ 'notification.rule-engine-events-trigger-settings' | translate }} +
    +
    +
    + notification.rule-engine-filter + + + + rulechain.rulechain-events + + + {{ componentLifecycleEventTranslationMap.get(componentLifecycleEvent) | translate }} + + + + + {{ 'notification.only-rule-chain-lifecycle-failures' | translate }} + +
    +
    + notification.rule-node-filter + + {{ 'notification.track-rule-node-events' | translate }} + +
    + + rulenode.rule-node-events + + + {{ componentLifecycleEventTranslationMap.get(componentLifecycleEvent) | translate }} + + + + + {{ 'notification.only-rule-node-lifecycle-failures' | translate }} + +
    +
    +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + + {{ 'notification.entities-limit-trigger-settings' | translate }} +
    +
    + notification.filter + + +
    + +
    + + + + + + +
    +
    +
    +
    +
    +
    + + notification.description + + +
    +
    +
    + + + {{ 'notification.api-usage-trigger-settings' | translate }} +
    +
    + + api-usage.api-features + + + {{ apiFeatureTranslationMap.get(apiFeature) | translate }} + + + {{ 'notification.api-feature-hint' | translate }} + + + notification.notify-on + + + {{ apiUsageStateValueTranslationMap.get(apiUsageStateValue) | translate }} + + + + {{ 'notification.notify-on-required' | translate }} + + +
    +
    +
    +
    + + notification.description + + +
    +
    +
    +
    +
    + +
    + + + +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss new file mode 100644 index 0000000000..7e582d2b7d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss @@ -0,0 +1,177 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../theme"; +@import "../../../../../../scss/constants"; + +:host { + width: 900px; + height: 100%; + max-width: 100%; + max-height: 100vh; + display: flex; + flex-direction: column; + + .tb-hint { + margin-top: -1.25em; + padding: 0; + } + + .fields-group { + padding: 0 16px; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + + &.tb-margin { + margin-bottom: 12px; + } + + &.tb-margin-before-field { + margin-bottom: 12px; + } + + &.tb-hierarchy { + padding: 0 0 8px 16px; + } + + legend { + margin-bottom: 8px; + color: rgba(0, 0, 0, .7); + width: fit-content; + } + + .mat-body-2 { + margin-bottom: 8px; + color: rgba(0,0,0,.76); + } + + .clear-button-space { + margin-right: 48px; + } + + .mat-mdc-slide-toggle { + margin-bottom: 16px; + } + } + + .limit-slider-container { + .limit-slider-value { + margin-left: 16px; + min-width: 25px; + max-width: 100px; + } + mat-form-field input[type=number] { + text-align: center; + } + } + + @media #{$mat-gt-sm} { + .history-time-input { + min-width: 364px; + } + .limit-slider-container { + > label { + margin-right: 16px; + width: min-content; + max-width: 40%; + } + } + } + + ::ng-deep { + + .mat-mdc-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + padding: 0 !important; + + .mat-stepper-horizontal { + display: flex; + height: 100%; + overflow: hidden; + + .mat-horizontal-stepper-wrapper { + flex: 1 1 100%; + } + + .mat-horizontal-content-container { + height: 700px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + scrollbar-gutter: stable; + @media #{$mat-gt-sm} { + min-width: 500px; + } + } + } + } + + .mat-button-toggle-group.tb-notification-unread-toggle-group { + &.mat-button-toggle-group-appearance-standard { + border: none; + border-radius: 18px; + margin-bottom: 8px; + + .mat-button-toggle + .mat-button-toggle { + border-left: none; + } + } + + .mat-button-toggle { + background: rgba(0, 0, 0, 0.06); + height: 36px; + align-items: center; + display: flex; + + .mat-button-toggle-ripple { + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border-radius: 18px; + } + } + + .mat-button-toggle-button { + color: #959595; + } + + .mat-button-toggle-focus-overlay { + border-radius: 18px; + margin: 2px; + } + + .mat-button-toggle-checked .mat-button-toggle-button { + background-color: $tb-primary-color; + color: #fff; + border-radius: 18px; + margin-left: 2px; + margin-right: 2px; + } + + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + line-height: 34px; + font-size: 16px; + font-weight: 500; + } + + .mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { + opacity: .01; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts new file mode 100644 index 0000000000..1461cf5db3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -0,0 +1,458 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AlarmAction, + AlarmActionTranslationMap, + AlarmAssignmentAction, + AlarmAssignmentActionTranslationMap, + ComponentLifecycleEvent, + ComponentLifecycleEventTranslationMap, + DeviceEvent, + DeviceEventTranslationMap, + NotificationRule, + NotificationTarget, + TriggerType, + TriggerTypeTranslationMap +} from '@shared/models/notification.models'; +import { Component, Inject, OnDestroy, ViewChild } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { NotificationService } from '@core/http/notification.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { deepClone, deepTrim, isDefined } from '@core/utils'; +import { Observable, Subject } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; +import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; +import { MatStepper } from '@angular/material/stepper'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { + AlarmSearchStatus, + alarmSearchStatusTranslations, + AlarmSeverity, + alarmSeverityTranslations +} from '@shared/models/alarm.models'; +import { TranslateService } from '@ngx-translate/core'; +import { + RecipientNotificationDialogComponent, + RecipientNotificationDialogData +} from '@home/pages/notification/recipient/recipient-notification-dialog.component'; +import { MatButton } from '@angular/material/button'; +import { AuthState } from '@core/auth/auth.models'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; +import { AuthUser } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; +import { + ApiFeature, + ApiFeatureTranslationMap, + ApiUsageStateValue, + ApiUsageStateValueTranslationMap +} from '@shared/models/api-usage.models'; + +export interface RuleNotificationDialogData { + rule?: NotificationRule; + isAdd?: boolean; + isCopy?: boolean; +} + +@Component({ + selector: 'tb-rule-notification-dialog', + templateUrl: './rule-notification-dialog.component.html', + styleUrls: ['rule-notification-dialog.component.scss'] +}) +export class RuleNotificationDialogComponent extends + DialogComponent implements OnDestroy { + + @ViewChild('addNotificationRule', {static: true}) addNotificationRule: MatStepper; + + stepperOrientation: Observable; + + ruleNotificationForm: FormGroup; + alarmTemplateForm: FormGroup; + deviceInactivityTemplateForm: FormGroup; + entityActionTemplateForm: FormGroup; + alarmCommentTemplateForm: FormGroup; + alarmAssignmentTemplateForm: FormGroup; + ruleEngineEventsTemplateForm: FormGroup; + entitiesLimitTemplateForm: FormGroup; + apiUsageLimitTemplateForm: FormGroup; + + triggerType = TriggerType; + triggerTypes: TriggerType[]; + triggerTypeTranslationMap = TriggerTypeTranslationMap; + + alarmSearchStatuses = [ + AlarmSearchStatus.ACTIVE, + AlarmSearchStatus.CLEARED, + AlarmSearchStatus.ACK, + AlarmSearchStatus.UNACK + ]; + alarmSearchStatusTranslationMap = alarmSearchStatusTranslations; + + alarmSeverityTranslationMap = alarmSeverityTranslations; + alarmSeverities = Object.keys(AlarmSeverity) as Array; + + alarmActions: AlarmAction[] = Object.values(AlarmAction); + alarmActionTranslationMap = AlarmActionTranslationMap; + + alarmAssignmentActions: AlarmAssignmentAction[] = Object.values(AlarmAssignmentAction); + alarmAssignmentActionTranslationMap = AlarmAssignmentActionTranslationMap; + + componentLifecycleEvents: ComponentLifecycleEvent[] = Object.values(ComponentLifecycleEvent); + componentLifecycleEventTranslationMap = ComponentLifecycleEventTranslationMap; + + deviceEvents: DeviceEvent[] = Object.values(DeviceEvent); + deviceEventTranslationMap = DeviceEventTranslationMap; + + apiUsageStateValues: ApiUsageStateValue[] = Object.values(ApiUsageStateValue); + apiUsageStateValueTranslationMap = ApiUsageStateValueTranslationMap; + + apiFeatures: ApiFeature[] = Object.values(ApiFeature); + apiFeatureTranslationMap = ApiFeatureTranslationMap; + + entityType = EntityType; + isAdd = true; + + allowEntityTypeForEntitiesLimit = [ + EntityType.DEVICE, + EntityType.ASSET, + EntityType.CUSTOMER, + EntityType.USER, + EntityType.DASHBOARD, + EntityType.RULE_CHAIN + ]; + + selectedIndex = 0; + + dialogTitle = 'notification.edit-rule'; + + private destroy$ = new Subject(); + + private readonly ruleNotification: NotificationRule; + + private triggerTypeFormsMap: Map; + private authState: AuthState = getCurrentAuthState(this.store); + private authUser: AuthUser = this.authState.authUser; + private _allowEntityTypeForEntityAction: EntityType[]; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: RuleNotificationDialogData, + private breakpointObserver: BreakpointObserver, + private fb: FormBuilder, + public translate: TranslateService, + private notificationService: NotificationService, + private dialog: MatDialog) { + super(store, router, dialogRef); + + this.triggerTypes = this.allowTriggerTypes(); + + if (isDefined(data.isAdd)) { + this.isAdd = data.isAdd; + } + + this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs']) + .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); + + this.ruleNotificationForm = this.fb.group({ + name: [null, Validators.required], + templateId: [null, Validators.required], + triggerType: [this.isSysAdmin() ? TriggerType.ENTITIES_LIMIT : TriggerType.ALARM, Validators.required], + recipientsConfig: this.fb.group({ + targets: [{value: null, disabled: !this.isSysAdmin()}, Validators.required], + escalationTable: [{value: null, disabled: this.isSysAdmin()}, Validators.required] + }), + triggerConfig: [null], + additionalConfig: this.fb.group({ + description: [''] + }) + }); + + this.ruleNotificationForm.get('triggerType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value === TriggerType.ALARM) { + this.ruleNotificationForm.get('recipientsConfig.escalationTable').enable({emitEvent: false}); + this.ruleNotificationForm.get('recipientsConfig.targets').disable({emitEvent: false}); + } else { + this.ruleNotificationForm.get('recipientsConfig.escalationTable').disable({emitEvent: false}); + this.ruleNotificationForm.get('recipientsConfig.targets').enable({emitEvent: false}); + } + }); + + this.ruleNotificationForm.get('recipientsConfig.escalationTable').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { + if (this.countRecipientsChainConfig() > 1) { + this.alarmTemplateForm.get('triggerConfig.clearRule').enable({emitEvent: false}); + } else { + this.alarmTemplateForm.get('triggerConfig.clearRule').disable({emitEvent: false}); + } + }); + + this.alarmTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + alarmTypes: [null], + alarmSeverities: [[]], + clearRule: this.fb.group({ + alarmStatuses: [[]] + }), + notifyOn: [[AlarmAction.CREATED], Validators.required] + }) + }); + + this.deviceInactivityTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + filterByDevice: [true], + devices: [null], + deviceProfiles: [{value: null, disabled: true}], + notifyOn: [[DeviceEvent.INACTIVE], Validators.required] + }) + }); + + this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value) { + this.deviceInactivityTemplateForm.get('triggerConfig.devices').enable({emitEvent: false}); + this.deviceInactivityTemplateForm.get('triggerConfig.deviceProfiles').disable({emitEvent: false}); + } else { + this.deviceInactivityTemplateForm.get('triggerConfig.deviceProfiles').enable({emitEvent: false}); + this.deviceInactivityTemplateForm.get('triggerConfig.devices').disable({emitEvent: false}); + } + }); + + this.entityActionTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + entityTypes: [[EntityType.DEVICE], Validators.required], + created: [false], + updated: [false], + deleted: [false] + }) + }); + + this.alarmCommentTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + alarmTypes: [null], + alarmSeverities: [[]], + alarmStatuses: [[]], + onlyUserComments: [false], + notifyOnCommentUpdate: [false] + }) + }); + + this.alarmAssignmentTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + alarmTypes: [null], + alarmSeverities: [[]], + alarmStatuses: [[]], + notifyOn: [[AlarmAssignmentAction.ASSIGNED], Validators.required] + }) + }); + + this.ruleEngineEventsTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + ruleChains: [null], + ruleChainEvents: [null], + onlyRuleChainLifecycleFailures: [false], + trackRuleNodeEvents: [false], + ruleNodeEvents: [null], + onlyRuleNodeLifecycleFailures: [false] + }) + }); + + this.entitiesLimitTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + entityTypes: [], + threshold: [.8, [Validators.min(0), Validators.max(1)]] + }) + }); + + this.apiUsageLimitTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + apiFeatures: [[]], + notifyOn: [[ApiUsageStateValue.WARNING], Validators.required] + }) + }); + + this.triggerTypeFormsMap = new Map([ + [TriggerType.ALARM, this.alarmTemplateForm], + [TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm], + [TriggerType.DEVICE_ACTIVITY, this.deviceInactivityTemplateForm], + [TriggerType.ENTITY_ACTION, this.entityActionTemplateForm], + [TriggerType.ALARM_ASSIGNMENT, this.alarmAssignmentTemplateForm], + [TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, this.ruleEngineEventsTemplateForm], + [TriggerType.ENTITIES_LIMIT, this.entitiesLimitTemplateForm], + [TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm] + ]); + + if (data.isAdd || data.isCopy) { + this.dialogTitle = 'notification.add-rule'; + } + this.ruleNotification = deepClone(this.data.rule); + + if (this.ruleNotification) { + if (this.data.isCopy) { + this.ruleNotification.name += ` (${this.translate.instant('action.copy')})`; + } else { + this.ruleNotificationForm.get('triggerType').disable({emitEvent: false}); + } + this.ruleNotificationForm.reset({}, {emitEvent: false}); + this.ruleNotificationForm.patchValue(this.ruleNotification, {emitEvent: false}); + this.ruleNotificationForm.get('triggerType').updateValueAndValidity({onlySelf: true}); + const currentForm = this.triggerTypeFormsMap.get(this.ruleNotification.triggerType); + currentForm.patchValue(this.ruleNotification, {emitEvent: false}); + if (this.ruleNotification.triggerType === TriggerType.DEVICE_ACTIVITY) { + this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice') + .patchValue(!!this.ruleNotification.triggerConfig.devices, {onlySelf: true}); + } + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); + } + + changeStep($event: StepperSelectionEvent) { + this.selectedIndex = $event.selectedIndex; + } + + backStep() { + this.addNotificationRule.previous(); + } + + nextStep() { + if (this.selectedIndex >= this.maxStepperIndex) { + this.add(); + } else { + this.addNotificationRule.next(); + } + } + + nextStepLabel(): string { + if (this.selectedIndex !== 0) { + return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save'; + } + return 'action.next'; + } + + private get maxStepperIndex(): number { + return this.addNotificationRule?._steps?.length - 1; + } + + private add(): void { + if (this.allValid()) { + let formValue = this.ruleNotificationForm.value; + const triggerType: TriggerType = this.ruleNotificationForm.get('triggerType').value; + const currentForm = this.triggerTypeFormsMap.get(triggerType); + Object.assign(formValue, currentForm.value); + if (triggerType === TriggerType.DEVICE_ACTIVITY) { + delete formValue.triggerConfig.filterByDevice; + } + formValue.recipientsConfig.triggerType = triggerType; + formValue.triggerConfig.triggerType = triggerType; + if (this.ruleNotification && !this.data.isCopy) { + formValue = {...this.ruleNotification, ...formValue}; + } + this.notificationService.saveNotificationRule(deepTrim(formValue)).subscribe( + (target) => this.dialogRef.close(target) + ); + } + } + + private allValid(): boolean { + return !this.addNotificationRule.steps.find((item, index) => { + if (item.stepControl.invalid) { + item.interacted = true; + this.addNotificationRule.selectedIndex = index; + return true; + } else { + return false; + } + }); + } + + cancel(): void { + this.dialogRef.close(null); + } + + createTarget($event: Event, button: MatButton) { + if ($event) { + $event.stopPropagation(); + } + button._elementRef.nativeElement.blur(); + this.dialog.open(RecipientNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: {} + }).afterClosed() + .subscribe((res) => { + if (res) { + let formValue: string[] = this.ruleNotificationForm.get('recipientsConfig.targets').value; + if (!formValue) { + formValue = []; + } + formValue.push(res.id.id); + this.ruleNotificationForm.get('recipientsConfig.targets').patchValue(formValue); + } + }); + } + + countRecipientsChainConfig(): number { + return Object.keys(this.ruleNotificationForm.get('recipientsConfig.escalationTable').value ?? {}).length; + } + + formatLabel(value: number): string { + const formatValue = (value * 100).toFixed(); + return `${formatValue}%`; + } + + private isSysAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; + } + + private allowTriggerTypes(): TriggerType[] { + if (this.isSysAdmin()) { + return [TriggerType.ENTITIES_LIMIT, TriggerType.API_USAGE_LIMIT]; + } + return Object.values(TriggerType).filter(type => type !== TriggerType.ENTITIES_LIMIT && type !== TriggerType.API_USAGE_LIMIT); + } + + get allowEntityTypeForEntityAction(): EntityType[] { + if (!this._allowEntityTypeForEntityAction) { + const excludeEntityType: Set = new Set([ + EntityType.API_USAGE_STATE, + EntityType.TENANT_PROFILE, + EntityType.RPC, + EntityType.QUEUE, + EntityType.NOTIFICATION, + EntityType.NOTIFICATION_REQUEST, + EntityType.WIDGET_TYPE + ]); + this._allowEntityTypeForEntityAction = Object.values(EntityType).filter(type => !excludeEntityType.has(type)); + } + return this._allowEntityTypeForEntityAction; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts new file mode 100644 index 0000000000..4d8962150f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts @@ -0,0 +1,131 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + CellActionDescriptor, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { NotificationRule, TriggerTypeTranslationMap } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { RuleTableHeaderComponent } from '@home/pages/notification/rule/rule-table-header.component'; +import { + RuleNotificationDialogComponent, + RuleNotificationDialogData +} from '@home/pages/notification/rule/rule-notification-dialog.component'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { DatePipe } from '@angular/common'; + +@Injectable() +export class RuleTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.NOTIFICATION_RULE; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.rowPointer = true; + + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION_RULE); + this.config.entityResources = {} as EntityTypeResource; + + this.config.entitiesFetchFunction = pageLink => this.notificationService.getNotificationRules(pageLink); + + this.config.deleteEntityTitle = rule => this.translate.instant('notification.delete-rule-title', {ruleName: rule.name}); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-rule-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-rules-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-rules-text'); + this.config.deleteEntity = id => this.notificationService.deleteNotificationRule(id.id); + + this.config.cellActionDescriptors = this.configureCellActions(); + this.config.headerComponent = RuleTableHeaderComponent; + this.config.onEntityAction = action => this.onTargetAction(action); + + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.config.handleRowClick = ($event, rule) => { + this.editRule($event, rule); + return true; + }; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('name', 'notification.rule-name', '30%'), + new EntityTableColumn('templateName', 'notification.template', '20%'), + new EntityTableColumn('triggerType', 'notification.trigger.trigger', '20%', + (rule) => this.translate.instant(TriggerTypeTranslationMap.get(rule.triggerType)) || '', + () => ({}), true), + new EntityTableColumn('additionalConfig.description', 'notification.description', '30%', + (target) => target.additionalConfig.description || '', + () => ({}), false) + ); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private configureCellActions(): Array> { + return [{ + name: this.translate.instant('notification.copy-rule'), + icon: 'content_copy', + isEnabled: () => true, + onAction: ($event, entity) => this.editRule($event, entity, false, true) + }]; + } + + private editRule($event: Event, rule: NotificationRule, isAdd = false, isCopy = false) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(RuleNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + isCopy, + rule + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.updateData(); + } + }); + } + + private onTargetAction(action: EntityAction): boolean { + switch (action.action) { + case 'add': + this.editRule(action.event, action.entity, true); + return true; + } + return false; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.html new file mode 100644 index 0000000000..b466fa2d06 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.html @@ -0,0 +1,24 @@ + +
    + +
    diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.scss b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.scss similarity index 80% rename from ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.scss index 45ce4ac603..33537b4b23 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - .mat-form-field-infix { - width: auto; - min-width: 100px; - } +:host{ + width: 10000px; } diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.ts new file mode 100644 index 0000000000..c6270310d6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-header.component.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { NotificationRule } from '@shared/models/notification.models'; + +@Component({ + selector: 'tb-rule-table-header', + templateUrl: './rule-table-header.component.html', + styleUrls: ['rule-table-header.component.scss'] +}) +export class RuleTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } + + createTarget($event: Event) { + this.entitiesTableConfig.onEntityAction({event: $event, action: 'add', entity: null}); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.html new file mode 100644 index 0000000000..654c75f515 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.html @@ -0,0 +1,40 @@ + + +

    notification.failed-send

    + + +
    +
    +
    +
    {{ notificationDeliveryMethodTranslateMap.get(errorStat.key) | translate }}
    + + + + + +
    + {{ error.key }} + {{ error.value }}
    + +
    +
    diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.scss b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.scss similarity index 53% rename from ui-ngx/src/app/shared/components/time/datetime.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.scss index 9d54f69b90..95301b4daa 100644 --- a/ui-ngx/src/app/shared/components/time/datetime.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - .mat-form-field-wrapper { - padding-bottom: 8px; +:host{ + display: block; + width: 600px; + max-width: 100%; + + h6 { + font-weight: 500; + font-size: 16px; + line-height: 20px; + margin-top: 0; } - .mat-form-field-underline { - bottom: 8px; - } - .mat-form-field-infix { - width: auto; - min-width: 100px; - } - mat-form-field { - &.no-label { - .mat-form-field-infix { - border-top-width: 0.2em; - } + + .tb-table-list { + table-layout: fixed; + width: 100%; + border: hidden; + border-collapse: collapse; + + td { + border: 8px solid transparent; + border-collapse: collapse; + font-size: 14px; } + + .tb-row-error { + word-wrap: break-word; + } + } + + .mat-divider { + margin: 24px 0; } } diff --git a/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.ts new file mode 100644 index 0000000000..7ce286717a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-error-dialog.component.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { + NotificationDeliveryMethod, + NotificationDeliveryMethodTranslateMap, + NotificationRequest +} from '@shared/models/notification.models'; + +export interface NotificationRequestErrorDialogData { + notificationRequest: NotificationRequest; +} + +@Component({ + selector: 'tb-notification-send-error-dialog', + templateUrl: './sent-error-dialog.component.html', + styleUrls: ['sent-error-dialog.component.scss'] +}) +export class SentErrorDialogComponent extends DialogComponent { + + errorStats: { [key in NotificationDeliveryMethod]: {[errorKey in string]: string}}; + + notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: NotificationRequestErrorDialogData) { + super(store, router, dialogRef); + + this.errorStats = data.notificationRequest.stats.errors; + } + + cancel(): void { + this.dialogRef.close(null); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html new file mode 100644 index 0000000000..3aa7c98069 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html @@ -0,0 +1,363 @@ + + +

    {{ dialogTitle | translate }}

    + + +
    + + +
    +
    + + + check + + + {{ 'notification.compose' | translate }} +
    +
    + + {{ 'notification.start-from-scratch' | translate }} + {{ 'notification.use-template' | translate }} + +
    +
    + + + +
    + + +
    +
    + +
    notification.at-least-one-should-be-selected
    +
    +
    + + {{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }} + +
    +
    +
    +
    +
    + + + + + +
    + + {{ 'notification.scheduler-later' | translate }} + +
    + + + + notification.time + + + + +
    +
    +
    +
    + + {{ 'notification.delivery-method.web' | translate }} +
    + {{ 'notification.input-fields-support-templatization' | translate}} + +
    +
    + + notification.subject + + + {{ 'notification.subject-required' | translate }} + + + + notification.message + + + {{ 'notification.message-required' | translate }} + + +
    +
    + + {{ 'icon.icon' | translate }} + +
    + + + + +
    +
    +
    + + {{ 'notification.action-button' | translate }} + +
    +
    + + notification.button-text + + + {{ 'notification.button-text-required' | translate }} + + +
    +
    + + notification.action-type + + + {{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }} + + + + + notification.link + + + {{ 'notification.link-required' | translate }} + + + + + + + + +
    + + {{ 'notification.set-entity-from-notification' | translate }} + +
    +
    +
    +
    +
    + + {{ 'notification.delivery-method.email' | translate }} + +
    + {{ 'notification.input-fields-support-templatization' | translate}} + +
    +
    + + notification.subject + + + {{ 'notification.subject-required' | translate }} + + + notification.message + +
    +
    +
    + + {{ 'notification.delivery-method.sms' | translate }} +
    + {{ 'notification.input-field-support-templatization' | translate}} + +
    +
    + + notification.message + + + {{ 'notification.message-required' | translate }} + + +
    +
    + + {{ 'notification.delivery-method.slack' | translate }} +
    + {{ 'notification.input-field-support-templatization' | translate}} + +
    +
    + + notification.message + + + {{ 'notification.message-required' | translate }} + + +
    +
    + + {{ 'notification.review' | translate }} + + +
    +
    +
    + +
    notification.delivery-method.web-preview
    +
    +
    + +
    +
    +
    +
    + +
    notification.delivery-method.email-preview
    +
    +
    +
    {{ preview.processedTemplates.EMAIL.subject }}
    + +
    +
    +
    +
    +
    + +
    notification.delivery-method.sms-preview
    +
    +
    + {{ preview.processedTemplates.SMS.body }} +
    +
    +
    +
    + +
    notification.delivery-method.slack-preview
    +
    +
    + {{ preview.processedTemplates.SLACK.body }} +
    +
    +
    +
    + supervisor_account +
    {{ 'notification.recipients-count' | translate : {count: preview.totalRecipientsCount} }}
    +
    +
    +
    + {{ detail.value }}{{ detail.key }} +
    +
    + + + + {{ recipientTitle }} + + +
    +
    +
    +
    +
    + +
    + + + +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss new file mode 100644 index 0000000000..0f2ca8a228 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss @@ -0,0 +1,241 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../scss/constants"; +@import "../../../../../../theme"; + +:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) { + width: 820px; + height: 100%; + max-width: 100%; + max-height: 100vh; + display: flex; + flex-direction: column; + + .tb-title { + font-size: 16px; + line-height: 24px; + } + + .tb-hint { + padding: 0 0 8px; + } + + .tb-hint-available-params { + border-radius: 6px; + background-color: rgba(48, 86, 128, 0.04); + margin-bottom: 8px; + padding: 8px 16px; + + .content { + vertical-align: middle; + } + } + + .delivery-method-container { + padding-bottom: 8px; + + &.even { + padding-right: 8px; + } + + .delivery-method { + padding: 16px 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 6px; + width: 100%; + height: 100%; + } + } + + .additional-config-group { + padding: 16px 16px 0; + margin-bottom: 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 6px; + width: 100%; + height: 100%; + + .toggle { + margin-bottom: 16px; + } + } + + .preview-group { + padding: 16px; + margin-bottom: 10px; + border: 1px groove rgba(0, 0, 0, .12); + border-radius: 4px; + + &.notification { + background-color: #F3F6FA; + } + + .group-title { + font-weight: 500; + font-size: 14px; + } + + &> div:not(:last-child) { + margin-bottom: 12px; + } + + .details-recipient { + font-size: 14px; + line-height: 14px; + + .number { + font-weight: 500; + margin-right: 4px; + } + + &:not(:last-of-type) { + margin-bottom: 8px; + } + } + + .divider { + margin-bottom: 8px; + } + + .web-preview { + flex-direction: row; + align-items: center; + justify-content: center; + display: flex; + + tb-notification { + border: 1px groove $tb-primary-color; + border-radius: 4px; + max-width: 400px; + width: 80%; + background-color: #fff; + } + } + + .notification-content { + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 4px; + background: #fff; + padding: 12px 16px; + + .subject { + padding-bottom: 12px; + font-weight: 500; + letter-spacing: 0.25px; + } + + .html-content { + margin-top: 12px; + font-weight: 400; + font-size: 14px; + line-height: 20px; + } + } + } +} + +:host ::ng-deep { + .mat-mdc-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + padding: 0 !important; + + .mat-stepper-horizontal { + display: flex; + height: 100%; + overflow: hidden; + + .mat-horizontal-stepper-wrapper { + flex: 1 1 100%; + } + + .mat-horizontal-content-container { + height: 680px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + scrollbar-gutter: stable; + @media #{$mat-gt-sm} { + min-width: 500px; + } + } + } + } + + .mat-button-toggle-group.tb-notification-use-template-toggle-group { + &.mat-button-toggle-group-appearance-standard { + border: none; + border-radius: 18px; + margin-bottom: 24px; + + .mat-button-toggle + .mat-button-toggle { + border-left: none; + } + } + + .mat-button-toggle { + background: rgba(0, 0, 0, 0.06); + height: 36px; + align-items: center; + display: flex; + + .mat-button-toggle-ripple { + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border-radius: 18px; + } + } + + .mat-button-toggle-button { + color: #959595; + } + + .mat-button-toggle-focus-overlay { + border-radius: 18px; + margin: 2px; + } + + .mat-button-toggle-checked .mat-button-toggle-button { + background-color: $tb-primary-color; + color: #fff; + border-radius: 18px; + margin-left: 2px; + margin-right: 2px; + } + + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + line-height: 34px; + font-size: 16px; + font-weight: 500; + } + + .mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { + opacity: .01; + } + } + + .preview-group { + .notification-content { + .html-content, + .html-content * { + all: revert; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts new file mode 100644 index 0000000000..4efb374ff1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts @@ -0,0 +1,275 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + NotificationDeliveryMethod, + NotificationDeliveryMethodTranslateMap, + NotificationRequest, + NotificationRequestPreview, + NotificationTarget, + NotificationType +} from '@shared/models/notification.models'; +import { Component, Inject, OnDestroy, ViewChild } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { NotificationService } from '@core/http/notification.service'; +import { deepTrim, guid, isDefinedAndNotNull } from '@core/utils'; +import { Observable } from 'rxjs'; +import { EntityType } from '@shared/models/entity-type.models'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { MatStepper } from '@angular/material/stepper'; +import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { map, takeUntil } from 'rxjs/operators'; +import { getCurrentTime } from '@shared/models/time/time.models'; +import { + RecipientNotificationDialogComponent, + RecipientNotificationDialogData +} from '@home/pages/notification/recipient/recipient-notification-dialog.component'; +import { MatButton } from '@angular/material/button'; +import { TemplateConfiguration } from '@home/pages/notification/template/template-configuration'; + +export interface RequestNotificationDialogData { + request?: NotificationRequest; + isAdd?: boolean; +} + +@Component({ + selector: 'tb-sent-notification-dialog', + templateUrl: './sent-notification-dialog.component.html', + styleUrls: ['./sent-notification-dialog.component.scss'] +}) +export class SentNotificationDialogComponent extends + TemplateConfiguration implements OnDestroy { + + @ViewChild('createNotification', {static: true}) createNotification: MatStepper; + stepperOrientation: Observable; + stepperLabelPosition: Observable<'bottom' | 'end'>; + + isAdd = true; + entityType = EntityType; + notificationType = NotificationType; + + notificationRequestForm: FormGroup; + + notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[]; + notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; + + selectedIndex = 0; + preview: NotificationRequestPreview = null; + + dialogTitle = 'notification.notify-again'; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: RequestNotificationDialogData, + private breakpointObserver: BreakpointObserver, + protected fb: FormBuilder, + private notificationService: NotificationService, + private dialog: MatDialog) { + super(store, router, dialogRef, fb); + + this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm']) + .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); + + this.stepperLabelPosition = this.breakpointObserver.observe(MediaBreakpoints['gt-md']) + .pipe(map(({matches}) => matches ? 'end' : 'bottom')); + + this.notificationRequestForm = this.fb.group({ + useTemplate: [false], + templateId: [{value: null, disabled: true}, Validators.required], + targets: [null, Validators.required], + template: this.templateNotificationForm, + additionalConfig: this.fb.group({ + enabled: [false], + timezone: [{value: '', disabled: true}, Validators.required], + time: [{value: 0, disabled: true}, Validators.required] + }) + }); + + this.notificationRequestForm.get('template.name').setValue(guid()); + + this.notificationRequestForm.get('useTemplate').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value) { + this.notificationRequestForm.get('templateId').enable({emitEvent: false}); + this.notificationRequestForm.get('template').disable({emitEvent: false}); + } else { + this.notificationRequestForm.get('templateId').disable({emitEvent: false}); + this.notificationRequestForm.get('template').enable({emitEvent: false}); + } + }); + + this.notificationRequestForm.get('additionalConfig.enabled').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value) { + this.notificationRequestForm.get('additionalConfig.timezone').enable({emitEvent: false}); + this.notificationRequestForm.get('additionalConfig.time').enable({emitEvent: false}); + } else { + this.notificationRequestForm.get('additionalConfig.timezone').disable({emitEvent: false}); + this.notificationRequestForm.get('additionalConfig.time').disable({emitEvent: false}); + } + }); + + if (data.isAdd) { + this.dialogTitle = 'notification.new-notification'; + } + if (data.request) { + this.notificationRequestForm.reset({}, {emitEvent: false}); + this.notificationRequestForm.patchValue(this.data.request, {emitEvent: false}); + this.notificationRequestForm.get('template.name').setValue(guid()); + let useTemplate = true; + if (isDefinedAndNotNull(this.data.request.template)) { + useTemplate = false; + // eslint-disable-next-line guard-for-in + for (const method in this.data.request.template.configuration.deliveryMethodsTemplates) { + this.deliveryMethodFormsMap.get(NotificationDeliveryMethod[method]) + .patchValue(this.data.request.template.configuration.deliveryMethodsTemplates[method]); + } + } + this.notificationRequestForm.get('useTemplate').setValue(useTemplate, {onlySelf : true}); + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + nextStepLabel(): string { + if (this.selectedIndex >= this.maxStepperIndex) { + return 'action.send'; + } + return 'action.next'; + } + + changeStep($event: StepperSelectionEvent) { + this.selectedIndex = $event.selectedIndex; + if (this.selectedIndex === this.maxStepperIndex) { + this.getPreview(); + } + } + + backStep() { + this.createNotification.previous(); + } + + nextStep() { + if (this.selectedIndex >= this.maxStepperIndex) { + this.add(); + } else { + this.createNotification.next(); + } + } + + private get maxStepperIndex(): number { + return this.createNotification?._steps?.length - 1; + } + + private add(): void { + if (this.allValid()) { + this.notificationService.createNotificationRequest(this.notificationFormValue).subscribe( + (notification) => this.dialogRef.close(notification) + ); + } + } + + private getPreview() { + if (this.allValid()) { + this.preview = null; + this.notificationService.getNotificationRequestPreview(this.notificationFormValue).pipe( + map(data => { + if (data.processedTemplates.WEB?.enabled) { + (data.processedTemplates.WEB as any).text = data.processedTemplates.WEB.body; + } + return data; + }) + ).subscribe( + (preview) => this.preview = preview + ); + } + } + + private get notificationFormValue(): NotificationRequest { + const formValue = deepTrim(this.notificationRequestForm.value); + if (!formValue.useTemplate) { + formValue.template = super.getNotificationTemplateValue(); + } + delete formValue.useTemplate; + let delay = 0; + if (formValue.additionalConfig.enabled) { + delay = (this.notificationRequestForm.value.additionalConfig.time.valueOf() - this.minDate().valueOf()) / 1000; + } + formValue.additionalConfig = { + sendingDelayInSec: delay > 0 ? delay : 0 + }; + return formValue; + } + + private allValid(): boolean { + return !this.createNotification.steps.find((item, index) => { + if (item.stepControl?.invalid) { + item.interacted = true; + this.createNotification.selectedIndex = index; + return true; + } else { + return false; + } + }); + } + + minDate(): Date { + return new Date(getCurrentTime(this.notificationRequestForm.get('additionalConfig.timezone').value).format('lll')); + } + + maxDate(): Date { + const date = this.minDate(); + date.setDate(date.getDate() + 7); + return date; + } + + createTarget($event: Event, button: MatButton) { + if ($event) { + $event.stopPropagation(); + } + button._elementRef.nativeElement.blur(); + this.dialog.open(RecipientNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: {} + }).afterClosed() + .subscribe((res) => { + if (res) { + let formValue: string[] = this.notificationRequestForm.get('targets').value; + if (!formValue) { + formValue = []; + } + formValue.push(res.id.id); + this.notificationRequestForm.get('targets').patchValue(formValue); + } + }); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/sent/sent-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-table-config.resolver.ts new file mode 100644 index 0000000000..290774b6e9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/sent/sent-table-config.resolver.ts @@ -0,0 +1,208 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + CellActionDescriptor, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { + NotificationDeliveryMethodTranslateMap, + NotificationRequest, + NotificationRequestInfo, + NotificationRequestStats, + NotificationRequestStatus, + NotificationRequestStatusTranslateMap, + NotificationTemplate +} from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { DatePipe } from '@angular/common'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { + RequestNotificationDialogData, + SentNotificationDialogComponent +} from '@home/pages/notification/sent/sent-notification-dialog.componet'; +import { PageLink } from '@shared/models/page/page-link'; +import { + NotificationRequestErrorDialogData, + SentErrorDialogComponent +} from '@home/pages/notification/sent/sent-error-dialog.component'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class SentTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = + new EntityTableConfig(); + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.NOTIFICATION_REQUEST; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.searchEnabled = false; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION_REQUEST); + this.config.entityResources = {} as EntityTypeResource; + + this.config.deleteEntityTitle = () => this.translate.instant('notification.delete-request-title'); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-request-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-requests-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-requests-text'); + + this.config.deleteEntity = id => this.notificationService.deleteNotificationRequest(id.id); + this.config.entitiesFetchFunction = pageLink => this.notificationService.getNotificationRequests(pageLink); + + this.config.cellActionDescriptors = this.configureCellActions(); + + this.config.onEntityAction = action => this.onRequestAction(action); + + this.config.handleRowClick = (event, entity) => { + if ((event.target as HTMLElement).getElementsByClassName('stats').length || (event.target as HTMLElement).className === 'stats') { + this.openStatsErrorDialog(event, entity); + } + return true; + }; + + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('status', 'notification.status', '15%', + request => `${this.requestStatus(request.status)}${this.requestStats(request.stats)}`, + request => this.requestStatusStyle(request.status)), + new EntityTableColumn('deliveryMethods', 'notification.delivery-method.delivery-method', '15%', + (request) => request.deliveryMethods + .map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '), + () => ({}), false), + new EntityTableColumn('templateName', 'notification.template', '70%') + ); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private configureCellActions(): Array> { + return [{ + name: this.translate.instant('notification.notify-again'), + mdiIcon: 'mdi:repeat-variant', + isEnabled: (request) => request.status !== NotificationRequestStatus.SCHEDULED, + onAction: ($event, entity) => this.createRequest($event, entity) + }]; + } + + private createRequest($event: Event, request: NotificationRequest, isAdd = false, updateData = true) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(SentNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + request + } + }).afterClosed().subscribe((res) => { + if (res && updateData) { + this.config.updateData(); + } + }); + } + + private onRequestAction(action: EntityAction): boolean { + switch (action.action) { + case 'add': + this.createRequest(action.event, action.entity, true); + return true; + case 'add-without-update': + this.createRequest(action.event, action.entity, true, false); + return true; + } + return false; + } + + private requestStatus(status: NotificationRequestStatus): string { + const translateKey = NotificationRequestStatusTranslateMap.get(status); + let backgroundColor = 'rgba(25, 128, 56, 0.08)'; + switch (status) { + case NotificationRequestStatus.SCHEDULED: + backgroundColor = 'rgba(48, 86, 128, 0.08)'; + break; + case NotificationRequestStatus.PROCESSING: + backgroundColor = 'rgba(212, 125, 24, 0.08)'; + break; + } + return `
    + ${this.translate.instant(translateKey)} +
    `; + } + + private requestStats(stats: NotificationRequestStats): string { + if (!stats?.errors) { + return ''; + } + let countError = 0; + Object.keys(stats.errors).forEach(method => countError += Object.keys(stats.errors[method]).length); + if (countError === 0) { + return ''; + } + return `
    + ${this.translate.instant('notification.fails', {count: countError})} > +
    `; + } + + private requestStatusStyle(status: NotificationRequestStatus): object { + const styleObj = { + fontSize: '14px', + color: '#198038' + }; + switch (status) { + case NotificationRequestStatus.SCHEDULED: + styleObj.color = '#305680'; + break; + case NotificationRequestStatus.PROCESSING: + styleObj.color = '#D47D18'; + break; + } + return styleObj; + } + + private openStatsErrorDialog($event: Event, notificationRequest: NotificationRequest) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(SentErrorDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + notificationRequest + } + }).afterClosed().subscribe(() => {}); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-configuration.ts b/ui-ngx/src/app/modules/home/pages/notification/template/template-configuration.ts new file mode 100644 index 0000000000..2eaf1807a0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-configuration.ts @@ -0,0 +1,201 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { + ActionButtonLinkType, + ActionButtonLinkTypeTranslateMap, + NotificationDeliveryMethod, + NotificationDeliveryMethodTranslateMap, + NotificationTemplate, + NotificationTemplateTypeTranslateMap, + NotificationType +} from '@shared/models/notification.models'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { Directive, OnDestroy } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MatDialogRef } from '@angular/material/dialog'; +import { deepClone, deepTrim } from '@core/utils'; + +@Directive() +// tslint:disable-next-line:directive-class-suffix +export abstract class TemplateConfiguration extends DialogComponent implements OnDestroy{ + + templateNotificationForm: FormGroup; + webTemplateForm: FormGroup; + emailTemplateForm: FormGroup; + smsTemplateForm: FormGroup; + slackTemplateForm: FormGroup; + + notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[]; + notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; + notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap; + + actionButtonLinkType = ActionButtonLinkType; + actionButtonLinkTypes = Object.keys(ActionButtonLinkType) as ActionButtonLinkType[]; + actionButtonLinkTypeTranslateMap = ActionButtonLinkTypeTranslateMap; + + tinyMceOptions: Record = { + base_url: '/assets/tinymce', + suffix: '.min', + plugins: ['link table image imagetools code fullscreen'], + menubar: 'edit insert tools view format table', + toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' + + '| link | table | image | alignleft aligncenter alignright alignjustify ' + + '| numlist bullist outdent indent | removeformat | code | fullscreen', + height: 400, + autofocus: false, + branding: false + }; + + protected readonly destroy$ = new Subject(); + + protected deliveryMethodFormsMap: Map; + + protected constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + protected fb: FormBuilder) { + super(store, router, dialogRef); + + this.templateNotificationForm = this.fb.group({ + name: ['', Validators.required], + notificationType: [NotificationType.GENERAL], + configuration: this.fb.group({ + deliveryMethodsTemplates: this.fb.group({}, {validators: this.atLeastOne()}) + }) + }); + + this.notificationDeliveryMethods.forEach(method => { + (this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup) + .addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.WEB}), {emitEvent: false}); + }); + + this.webTemplateForm = this.fb.group({ + subject: ['', Validators.required], + body: ['', Validators.required], + additionalConfig: this.fb.group({ + icon: this.fb.group({ + enabled: [false], + icon: [{value: 'notifications', disabled: true}, Validators.required], + color: ['#757575'] + }), + actionButtonConfig: this.fb.group({ + enabled: [false], + text: [{value: '', disabled: true}, Validators.required], + linkType: [ActionButtonLinkType.LINK], + link: [{value: '', disabled: true}, Validators.required], + dashboardId: [{value: null, disabled: true}, Validators.required], + dashboardState: [{value: null, disabled: true}], + setEntityIdInState: [{value: true, disabled: true}], + }), + }) + }); + + this.webTemplateForm.get('additionalConfig.icon.enabled').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (value) { + this.webTemplateForm.get('additionalConfig.icon.icon').enable({emitEvent: false}); + } else { + this.webTemplateForm.get('additionalConfig.icon.icon').disable({emitEvent: false}); + } + }); + + this.webTemplateForm.get('additionalConfig.actionButtonConfig.enabled').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (value) { + this.webTemplateForm.get('additionalConfig.actionButtonConfig').enable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.linkType').updateValueAndValidity({onlySelf: true}); + } else { + this.webTemplateForm.get('additionalConfig.actionButtonConfig').disable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.enabled').enable({emitEvent: false}); + } + }); + + this.webTemplateForm.get('additionalConfig.actionButtonConfig.linkType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + const isEnabled = this.webTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value; + if (isEnabled) { + if (value === ActionButtonLinkType.LINK) { + this.webTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').disable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').disable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').disable({emitEvent: false}); + } else { + this.webTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').enable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').enable({emitEvent: false}); + this.webTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').enable({emitEvent: false}); + } + } + }); + + this.emailTemplateForm = this.fb.group({ + subject: ['', Validators.required], + body: ['', Validators.required] + }); + + this.smsTemplateForm = this.fb.group({ + body: ['', Validators.required] + }); + + this.slackTemplateForm = this.fb.group({ + body: ['', Validators.required] + }); + + this.deliveryMethodFormsMap = new Map([ + [NotificationDeliveryMethod.WEB, this.webTemplateForm], + [NotificationDeliveryMethod.EMAIL, this.emailTemplateForm], + [NotificationDeliveryMethod.SMS, this.smsTemplateForm], + [NotificationDeliveryMethod.SLACK, this.slackTemplateForm] + ]); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + atLeastOne() { + return (group: FormGroup): ValidationErrors | null => { + let hasAtLeastOne = true; + if (group?.controls) { + const controlsFormValue: FormGroup[] = Object.entries(group.controls).map(method => method[1]) as any; + hasAtLeastOne = controlsFormValue.some(value => value.controls.enabled.value); + } + return hasAtLeastOne ? null : {atLeastOne: true}; + }; + } + + protected getNotificationTemplateValue(): NotificationTemplate { + const template: NotificationTemplate = deepClone(this.templateNotificationForm.value); + this.notificationDeliveryMethods.forEach(method => { + if (template.configuration.deliveryMethodsTemplates[method].enabled) { + Object.assign(template.configuration.deliveryMethodsTemplates[method], this.deliveryMethodFormsMap.get(method).value, {method}); + } else { + delete template.configuration.deliveryMethodsTemplates[method]; + } + }); + return deepTrim(template); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html new file mode 100644 index 0000000000..a7576f7265 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html @@ -0,0 +1,256 @@ + + +

    {{ dialogTitle | translate }}

    + + +
    + + +
    +
    + + + check + + + {{ 'notification.basic-settings' | translate }} +
    + + notification.name + + + {{ 'notification.name-required' | translate }} + + + + notification.type + + + {{ notificationTemplateTypeTranslateMap.get(notificationType).name | translate }} + + + +
    + +
    notification.at-least-one-should-be-selected
    +
    +
    + + {{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }} + +
    +
    +
    +
    +
    + + {{ 'notification.delivery-method.web' | translate }} +
    + {{ 'notification.input-fields-support-templatization' | translate}} + +
    +
    + + notification.subject + + + {{ 'notification.subject-required' | translate }} + + + + notification.message + + + {{ 'notification.message-required' | translate }} + + +
    +
    + + {{ 'icon.icon' | translate }} + +
    + + + + +
    +
    +
    + + {{ 'notification.action-button' | translate }} + +
    +
    + + notification.button-text + + + {{ 'notification.button-text-required' | translate }} + + +
    +
    + + notification.action-type + + + {{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }} + + + + + notification.link + + + {{ 'notification.link-required' | translate }} + + + + + + + + +
    + + {{ 'notification.set-entity-from-notification' | translate }} + +
    +
    +
    +
    +
    + + {{ 'notification.delivery-method.email' | translate }} + +
    + {{ 'notification.input-fields-support-templatization' | translate}} + +
    +
    + + notification.subject + + + {{ 'notification.subject-required' | translate }} + + + notification.message + +
    +
    +
    + + {{ 'notification.delivery-method.sms' | translate }} +
    + {{ 'notification.input-field-support-templatization' | translate}} + +
    +
    + + notification.message + + + {{ 'notification.message-required' | translate }} + + +
    +
    + + {{ 'notification.delivery-method.slack' | translate }} +
    + {{ 'notification.input-field-support-templatization' | translate}} + +
    +
    + + notification.message + + + {{ 'notification.message-required' | translate }} + + +
    +
    +
    +
    + +
    + + + +
    diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss new file mode 100644 index 0000000000..1fc1b427d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../scss/constants"; +@import "../../../../../../theme"; + +:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) { + width: 800px; + height: 100%; + max-width: 100%; + max-height: 100vh; + display: flex; + flex-direction: column; + + .tb-title { + font-size: 16px; + line-height: 24px; + } + + .tb-hint { + padding: 0 0 8px; + } + + .tb-hint-available-params { + border-radius: 6px; + background-color: rgba(48, 86, 128, 0.04); + margin-bottom: 8px; + padding: 8px 16px; + + .content { + vertical-align: middle; + } + } + + .delivery-method-container { + padding-bottom: 8px; + + &.even { + padding-right: 8px; + } + + .delivery-method { + padding: 16px 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 6px; + width: 100%; + height: 100%; + } + } + + .additional-config-group { + padding: 16px 16px 4px; + margin-bottom: 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 6px; + width: 100%; + height: 100%; + + .toggle { + margin-bottom: 12px; + } + } +} + +:host ::ng-deep { + .mat-mdc-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + padding: 0 !important; + + .mat-stepper-horizontal { + display: flex; + height: 100%; + overflow: hidden; + + .mat-horizontal-stepper-wrapper { + flex: 1 1 100%; + } + + .mat-horizontal-content-container { + height: 550px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + scrollbar-gutter: stable; + @media #{$mat-gt-sm} { + min-width: 500px; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts new file mode 100644 index 0000000000..a92afa7c8e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts @@ -0,0 +1,186 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NotificationDeliveryMethod, NotificationTemplate, NotificationType } from '@shared/models/notification.models'; +import { Component, Inject, OnDestroy, ViewChild } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder } from '@angular/forms'; +import { NotificationService } from '@core/http/notification.service'; +import { deepClone, isDefinedAndNotNull } from '@core/utils'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; +import { MatStepper } from '@angular/material/stepper'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { TranslateService } from '@ngx-translate/core'; +import { TemplateConfiguration } from '@home/pages/notification/template/template-configuration'; +import { AuthState } from '@core/auth/auth.models'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; +import { AuthUser } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; + +export interface TemplateNotificationDialogData { + template?: NotificationTemplate; + predefinedType?: NotificationType; + isAdd?: boolean; + isCopy?: boolean; +} + +@Component({ + selector: 'tb-template-notification-dialog', + templateUrl: './template-notification-dialog.component.html', + styleUrls: ['./template-notification-dialog.component.scss'] +}) +export class TemplateNotificationDialogComponent + extends TemplateConfiguration implements OnDestroy { + + @ViewChild('notificationTemplateStepper', {static: true}) notificationTemplateStepper: MatStepper; + + stepperOrientation: Observable; + stepperLabelPosition: Observable<'bottom' | 'end'>; + + dialogTitle = 'notification.edit-notification-template'; + + notificationTypes: NotificationType[]; + + selectedIndex = 0; + hideSelectType = false; + + private readonly templateNotification: NotificationTemplate; + private authState: AuthState = getCurrentAuthState(this.store); + private authUser: AuthUser = this.authState.authUser; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: TemplateNotificationDialogData, + private breakpointObserver: BreakpointObserver, + protected fb: FormBuilder, + private notificationService: NotificationService, + private translate: TranslateService) { + super(store, router, dialogRef, fb); + + this.notificationTypes = this.allowNotificationType(); + + this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm']) + .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); + + this.stepperLabelPosition = this.breakpointObserver.observe(MediaBreakpoints['gt-md']) + .pipe(map(({matches}) => matches ? 'end' : 'bottom')); + + if (isDefinedAndNotNull(this.data?.predefinedType)) { + this.hideSelectType = true; + this.templateNotificationForm.get('notificationType').setValue(this.data.predefinedType, {emitEvent: false}); + } + + if (data.isAdd || data.isCopy) { + this.dialogTitle = 'notification.add-notification-template'; + } + this.templateNotification = deepClone(this.data.template); + + if (this.templateNotification) { + if (this.data.isCopy) { + this.templateNotification.name += ` (${this.translate.instant('action.copy')})`; + } else { + this.templateNotificationForm.get('notificationType').disable({emitEvent: false}); + } + this.templateNotificationForm.reset({}, {emitEvent: false}); + this.templateNotificationForm.patchValue(this.templateNotification, {emitEvent: false}); + // eslint-disable-next-line guard-for-in + for (const method in this.templateNotification.configuration.deliveryMethodsTemplates) { + this.deliveryMethodFormsMap.get(NotificationDeliveryMethod[method]) + .patchValue(this.templateNotification.configuration.deliveryMethodsTemplates[method]); + } + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + changeStep($event: StepperSelectionEvent) { + this.selectedIndex = $event.selectedIndex; + } + + backStep() { + this.notificationTemplateStepper.previous(); + } + + nextStep() { + if (this.selectedIndex >= this.maxStepperIndex) { + this.add(); + } else { + this.notificationTemplateStepper.next(); + } + } + + nextStepLabel(): string { + if (this.selectedIndex >= this.maxStepperIndex) { + return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save'; + } + return 'action.next'; + } + + private get maxStepperIndex(): number { + return this.notificationTemplateStepper?._steps?.length - 1; + } + + private add(): void { + if (this.allValid()) { + let template = this.getNotificationTemplateValue(); + if (this.templateNotification && !this.data.isCopy) { + template = {...this.templateNotification, ...template}; + } + this.notificationService.saveNotificationTemplate(template).subscribe( + (target) => this.dialogRef.close(target) + ); + } + } + + private allValid(): boolean { + return !this.notificationTemplateStepper.steps.find((item, index) => { + if (item.stepControl.invalid) { + item.interacted = true; + this.notificationTemplateStepper.selectedIndex = index; + return true; + } else { + return false; + } + }); + } + + private isSysAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; + } + + private allowNotificationType(): NotificationType[] { + if (this.isSysAdmin()) { + return [NotificationType.GENERAL, NotificationType.ENTITIES_LIMIT, NotificationType.API_USAGE_LIMIT]; + } + return Object.values(NotificationType) + .filter(type => type !== NotificationType.ENTITIES_LIMIT && type !== NotificationType.API_USAGE_LIMIT); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-config.resolver.ts new file mode 100644 index 0000000000..34f0b42f12 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-config.resolver.ts @@ -0,0 +1,130 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + CellActionDescriptor, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { NotificationTemplate, NotificationTemplateTypeTranslateMap } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { MatDialog } from '@angular/material/dialog'; +import { + TemplateNotificationDialogComponent, + TemplateNotificationDialogData +} from '@home/pages/notification/template/template-notification-dialog.component'; +import { TranslateService } from '@ngx-translate/core'; +import { TemplateTableHeaderComponent } from '@home/pages/notification/template/template-table-header.component'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { DatePipe } from '@angular/common'; + +@Injectable() +export class TemplateTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.NOTIFICATION_TEMPLATE; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.rowPointer = true; + + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION_TEMPLATE); + this.config.entityResources = {} as EntityTypeResource; + + this.config.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTemplates(pageLink); + + this.config.deleteEntityTitle = template => this.translate.instant('notification.delete-template-title', {templateName: template.name}); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-template-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-templates-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-templates-text'); + this.config.deleteEntity = id => this.notificationService.deleteNotificationTemplate(id.id); + + this.config.cellActionDescriptors = this.configureCellActions(); + + this.config.headerComponent = TemplateTableHeaderComponent; + this.config.onEntityAction = action => this.onTemplateAction(action); + + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.config.handleRowClick = ($event, template) => { + this.editTemplate($event, template); + return true; + }; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('notificationType', 'notification.type', '20%', + (template) => this.translate.instant(NotificationTemplateTypeTranslateMap.get(template.notificationType).name)), + new EntityTableColumn('name', 'notification.template', '80%') + ); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private configureCellActions(): Array> { + return [ + { + name: this.translate.instant('notification.copy-template'), + icon: 'content_copy', + isEnabled: () => true, + onAction: ($event, entity) => this.editTemplate($event, entity, false, true) + } + ]; + } + + private editTemplate($event: Event, template: NotificationTemplate, isAdd = false, isCopy = false) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(TemplateNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + isCopy, + template + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.updateData(); + } + }); + } + + private onTemplateAction(action: EntityAction): boolean { + switch (action.action) { + case 'add': + this.editTemplate(action.event, action.entity, true); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.html new file mode 100644 index 0000000000..39fe86994e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.html @@ -0,0 +1,24 @@ + +
    + +
    diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.scss similarity index 79% rename from ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.scss index e5f3e6532f..33537b4b23 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - .source-attribute { - .mat-form-field-infix{ - width: 100%; - } - } +:host{ + width: 10000px; } diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.ts new file mode 100644 index 0000000000..f6fd3f7e75 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-table-header.component.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { NotificationTemplate } from '@shared/models/notification.models'; + +@Component({ + selector: 'tb-template-table-header', + templateUrl: './template-table-header.component.html', + styleUrls: ['template-table-header.component.scss'] +}) +export class TemplateTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } + + createTemplate($event: Event) { + this.entitiesTableConfig.onEntityAction({event: $event, action: 'add', entity: null}); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts index 9d0f01e663..2af6910e23 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { BreadCrumbConfig } from '@shared/components/breadcrumb'; -const routes: Routes = [ +export const otaUpdatesRoutes: Routes = [ { path: 'otaUpdates', data: { @@ -65,6 +65,18 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'otaUpdates', + pathMatch: 'full', + redirectTo: '/features/otaUpdates' + }, + { + path: 'otaUpdates/:entityId', + redirectTo: '/features/otaUpdates/:entityId' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts index 8d173ad759..cbadbc8553 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -73,6 +73,7 @@ export class OtaUpdateTableConfigResolve implements Resolve + [showDetailsPageLink]="true">
    ota-update.package-type diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts index 4e3db409dd..da479fa190 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { EntityComponent } from '@home/components/entity/entity.component'; import { ChecksumAlgorithm, @@ -39,7 +39,7 @@ import { isNotEmptyStr } from '@core/utils'; }) export class OtaUpdateComponent extends EntityComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); + private destroy$ = new Subject(); checksumAlgorithms = Object.values(ChecksumAlgorithm); checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap; @@ -50,7 +50,7 @@ export class OtaUpdateComponent extends EntityComponent implements O protected translate: TranslateService, @Inject('entity') protected entityValue: OtaPackage, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder, + public fb: UntypedFormBuilder, protected cd: ChangeDetectorRef) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } @@ -100,7 +100,7 @@ export class OtaUpdateComponent extends EntityComponent implements O } } - buildForm(entity: OtaPackage): FormGroup { + buildForm(entity: OtaPackage): UntypedFormGroup { const form = this.fb.group({ title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts index c768167a5e..1734b10f65 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts index 440db606fd..c14e450745 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html index 3a15616055..fad97beb68 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -1,6 +1,6 @@
    - - -
    + +
    - profile.profile + profile.profile {{ profile ? profile.get('email').value : '' }}
    - profile.last-login-time + profile.last-login-time
    - - - +
    @@ -56,6 +54,11 @@ user.last-name + + language.language @@ -67,7 +70,7 @@
    rulenode.link-labels - - + {{label.name}} close - + - + ; @@ -72,7 +72,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan @Input() sourceRuleChainId: string; - linksFormGroup: FormGroup; + linksFormGroup: UntypedFormGroup; modelValue: Array; @@ -88,7 +88,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan private propagateChange = (v: any) => { }; - constructor(private fb: FormBuilder, + constructor(private fb: UntypedFormBuilder, public truncate: TruncatePipe, public translate: TranslateService, private ruleChainService: RuleChainService) { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss index d16ba312ca..931f5ac6fc 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html index b7abe74096..97f2214f2e 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html @@ -1,6 +1,6 @@
    - + - profile.jwt-token + profile.jwt-token - -
    -
    {{ 'profile.token-valid-till' | translate }} {{ jwtTokenExpiration | date: 'yyyy-MM-dd HH:mm:ss' }}
    - -
    -
    +
    +
    {{ 'profile.token-valid-till' | translate }} {{ jwtTokenExpiration | date: 'yyyy-MM-dd HH:mm:ss' }}
    + +
    - - + +
    @@ -123,51 +121,49 @@
    - +
    - + - admin.2fa.2fa + admin.2fa.2fa -
    security.2fa.2fa-description
    +
    security.2fa.2fa-description
    - -

    security.2fa.authenticate-with

    -
    - -
    -

    {{ providersData.get(provider).name | translate }}

    -
    -
    - {{ providersData.get(provider).description | translate }} -
    - -
    - {{ providersData.get(provider).activatedHint | translate: providerDataInfo(provider) }} -
    -
    - - +

    security.2fa.authenticate-with

    + + +
    +

    {{ providersData.get(provider).name | translate }}

    +
    +
    + {{ providersData.get(provider).description | translate }}
    - - security.2fa.main-2fa-method - - + +
    + {{ providersData.get(provider).activatedHint | translate: providerDataInfo(provider) }} +
    +
    + +
    - - - - + + security.2fa.main-2fa-method + + +
    + +
    +
    diff --git a/ui-ngx/src/app/modules/home/pages/security/security.component.scss b/ui-ngx/src/app/modules/home/pages/security/security.component.scss index b593be3a2a..5a9f968275 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security.component.scss +++ b/ui-ngx/src/app/modules/home/pages/security/security.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ .profile-container { padding: 8px; } - - mat-card.profile-card { + .mat-mdc-card.profile-card { padding: 24px; @media #{$mat-gt-sm} { width: 80%; @@ -52,7 +51,7 @@ margin-bottom: 25px; } - .mat-form-field { + .mat-mdc-form-field { margin-bottom: 4px; } @@ -114,17 +113,12 @@ opacity: 0.87; } - .mat-checkbox { + .mat-mdc-checkbox { margin-top: 8px; } - .mat-stroked-button { + .mat-mdc-outlined-button { margin-top: 8px; } } } -:host ::ng-deep { - .mat-form-field-appearance-fill .mat-form-field-underline::before { - background-color: transparent; - } -} diff --git a/ui-ngx/src/app/modules/home/pages/security/security.component.ts b/ui-ngx/src/app/modules/home/pages/security/security.component.ts index 1ecfb66954..9a99e7d6d4 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security.component.ts +++ b/ui-ngx/src/app/modules/home/pages/security/security.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AbstractControl, - FormBuilder, - FormGroup, FormGroupDirective, + UntypedFormBuilder, + UntypedFormGroup, FormGroupDirective, NgForm, ValidationErrors, ValidatorFn, @@ -51,6 +51,7 @@ import { Observable, of, Subject } from 'rxjs'; import { isDefinedAndNotNull, isEqual } from '@core/utils'; import { AuthService } from '@core/auth/auth.service'; import { UserPasswordPolicy } from '@shared/models/settings.models'; +import { MatCheckboxChange } from '@angular/material/checkbox'; @Component({ selector: 'tb-security', @@ -62,8 +63,8 @@ export class SecurityComponent extends PageComponent implements OnInit, OnDestro private readonly destroy$ = new Subject(); private accountConfig: AccountTwoFaSettingProviders; - twoFactorAuth: FormGroup; - changePassword: FormGroup; + twoFactorAuth: UntypedFormGroup; + changePassword: UntypedFormGroup; user: User; passwordPolicy: UserPasswordPolicy; @@ -93,7 +94,7 @@ export class SecurityComponent extends PageComponent implements OnInit, OnDestro private twoFaService: TwoFactorAuthenticationService, public dialog: MatDialog, public dialogService: DialogService, - public fb: FormBuilder, + public fb: UntypedFormBuilder, private datePipe: DatePipe, private authService: AuthService, private clipboardService: ClipboardService) { @@ -290,14 +291,14 @@ export class SecurityComponent extends PageComponent implements OnInit, OnDestro }); } - changeDefaultProvider(event: MouseEvent, provider: TwoFactorAuthProviderType) { - event.stopPropagation(); - event.preventDefault(); + changeDefaultProvider(event: MatCheckboxChange, provider: TwoFactorAuthProviderType) { if (this.useByDefault !== provider) { this.twoFactorAuth.disable({emitEvent: false}); this.twoFaService.updateTwoFaAccountConfig(provider, true) .pipe(tap(() => this.twoFactorAuth.enable({emitEvent: false}))) .subscribe(data => this.processTwoFactorAuthConfig(data)); + } else { + event.source.checked = true; } } diff --git a/ui-ngx/src/app/modules/home/pages/security/security.module.ts b/ui-ngx/src/app/modules/home/pages/security/security.module.ts index 5db12c2a68..52d6636e0f 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security.module.ts +++ b/ui-ngx/src/app/modules/home/pages/security/security.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts b/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts index b2acabc0d5..f17b78021e 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html index 4aa21e2aa5..cec78fd60d 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html @@ -1,6 +1,6 @@
    - - - login.create-password - + + + + login.create-password + + @@ -28,13 +30,13 @@
    - + common.password lock - + login.password-again lock @@ -45,7 +47,7 @@ - diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss b/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss index 8ddc3b7982..e49395af06 100644 --- a/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts index 5c95dd8f87..f305d183c1 100644 --- a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import { FormBuilder } from '@angular/forms'; +import { UntypedFormBuilder } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; @@ -44,7 +44,7 @@ export class CreatePasswordComponent extends PageComponent implements OnInit, On private route: ActivatedRoute, private authService: AuthService, private translate: TranslateService, - public fb: FormBuilder) { + public fb: UntypedFormBuilder) { super(store); } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index ea814b4def..f5797ae574 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -1,6 +1,6 @@
    - + login.username email @@ -49,7 +49,7 @@ {{ 'user.invalid-email-format' | translate }} - + common.password diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index 5013345566..8b72d3ee3d 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ @import '../../../../../scss/constants'; +@import '../../../../../theme'; :host { display: flex; @@ -70,23 +71,17 @@ a.login-with-button { color: rgba(black, 0.87); + background-color: map-get($tb-dark-theme-background, raised-button); &:hover { border-bottom: 0; } - .icon{ + .icon { height: 20px; width: 20px; - vertical-align: sub; } } - - .centered ::ng-deep .mat-button-wrapper { - display: flex; - justify-content: center; - align-items: center; - } } } } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index e889969e4d..2fc1c7c50d 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import { FormBuilder } from '@angular/forms'; +import { UntypedFormBuilder } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; import { Router } from '@angular/router'; @@ -40,7 +40,7 @@ export class LoginComponent extends PageComponent implements OnInit { constructor(protected store: Store, private authService: AuthService, - public fb: FormBuilder, + public fb: UntypedFormBuilder, private router: Router) { super(store); } diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html index c7832f6c91..53357903c8 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html @@ -1,6 +1,6 @@
    - - - login.request-password-reset - + + + + login.request-password-reset + + @@ -29,7 +31,7 @@
    - + login.email email @@ -42,7 +44,7 @@ - diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss index 0b2dfc2e59..9e64eef74d 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts index 233cf5f32d..e51df631c9 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import { FormBuilder, Validators } from '@angular/forms'; +import { UntypedFormBuilder, Validators } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; @@ -39,7 +39,7 @@ export class ResetPasswordRequestComponent extends PageComponent implements OnIn constructor(protected store: Store, private authService: AuthService, private translate: TranslateService, - public fb: FormBuilder) { + public fb: UntypedFormBuilder) { super(store); } diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html index b0e0e6737b..bb45938716 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html @@ -1,6 +1,6 @@
    - - - login.password-reset - - -
    login.expired-password-reset-message
    -
    + + + + login.password-reset + + +
    login.expired-password-reset-message
    +
    +
    @@ -31,13 +33,13 @@
    - + login.new-password lock - + login.new-password-again lock @@ -48,7 +50,7 @@ - diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss index 41200be335..101054ac70 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts index ab0928ec66..68be67a9da 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import { FormBuilder } from '@angular/forms'; +import { UntypedFormBuilder } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; @@ -46,7 +46,7 @@ export class ResetPasswordComponent extends PageComponent implements OnInit, OnD private route: ActivatedRoute, private authService: AuthService, private translate: TranslateService, - public fb: FormBuilder) { + public fb: UntypedFormBuilder) { super(store); } diff --git a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html index f6592e686e..56565588cd 100644 --- a/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html @@ -1,6 +1,6 @@
    + +
    + + +
    diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index a8de9e6623..70f43f410c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,14 @@ @import "../../../../scss/constants"; :host { - width: 100%; - height: 100%; - form, - fieldset { - height: 100%; - } + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 100%; + background-color: #fff; .mat-content { overflow: hidden; - background-color: #fff; } .mat-padding { @@ -45,7 +43,7 @@ .limit-slider-value { margin-left: 16px; min-width: 25px; - max-width: 80px; + max-width: 100px; } mat-form-field input[type=number] { text-align: center; @@ -68,21 +66,16 @@ } :host ::ng-deep { - mat-radio-button { + .mat-mdc-radio-button { display: block; - margin-bottom: 16px; - .mat-radio-label { - width: 100%; + .mdc-form-field { align-items: start; - .mat-radio-label-content { - width: 100%; + > label { + padding-top: 10px; } } } - .mat-slider-horizontal .mat-slider-thumb-label { - width: 38px; - height: 38px; - top: -46px; - right: -19px; + .mat-mdc-tab-group:not(.tb-headless) { + height: 100%; } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 67ee2ac72b..7b30759cdf 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -25,14 +25,13 @@ import { Timewindow, TimewindowType } from '@shared/models/time/time.models'; -import { OverlayRef } from '@angular/cdk/overlay'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; - -export const TIMEWINDOW_PANEL_DATA = new InjectionToken('TimewindowPanelData'); +import { isDefined } from '@core/utils'; +import { OverlayRef } from '@angular/cdk/overlay'; export interface TimewindowPanelData { historyOnly: boolean; @@ -43,6 +42,8 @@ export interface TimewindowPanelData { isEdit: boolean; } +export const TIMEWINDOW_PANEL_DATA = new InjectionToken('TimewindowPanelData'); + @Component({ selector: 'tb-timewindow-panel', templateUrl: './timewindow-panel.component.html', @@ -62,9 +63,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { timewindow: Timewindow; - result: Timewindow; - - timewindowForm: FormGroup; + timewindowForm: UntypedFormGroup; historyTypes = HistoryWindowType; @@ -78,10 +77,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { aggregationTypesTranslations = aggregationTranslations; + result: Timewindow; + constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, public overlayRef: OverlayRef, protected store: Store, - public fb: FormBuilder, + public fb: UntypedFormBuilder, private timeService: TimeService, public viewContainerRef: ViewContainerRef) { super(store); @@ -101,77 +102,60 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { const hideAggInterval = this.timewindow.hideAggInterval || false; const hideTimezone = this.timewindow.hideTimezone || false; + const realtime = this.timewindow.realtime; + const history = this.timewindow.history; + const aggregation = this.timewindow.aggregation; + this.timewindowForm = this.fb.group({ - realtime: this.fb.group( - { - realtimeType: this.fb.control({ - value: this.timewindow.realtime && typeof this.timewindow.realtime.realtimeType !== 'undefined' - ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, - disabled: hideInterval - }), - timewindowMs: this.fb.control({ - value: this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined' - ? this.timewindow.realtime.timewindowMs : null, - disabled: hideInterval || hideLastInterval - }), - interval: [ - this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined' - ? this.timewindow.realtime.interval : null - ], - quickInterval: this.fb.control({ - value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined' - ? this.timewindow.realtime.quickInterval : null, - disabled: hideInterval || hideQuickInterval - }) - } - ), - history: this.fb.group( - { - historyType: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.historyType !== 'undefined' - ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL, - disabled: hideInterval - }), - timewindowMs: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined' - ? this.timewindow.history.timewindowMs : null, - disabled: hideInterval - }), - interval: [ - this.timewindow.history && typeof this.timewindow.history.interval !== 'undefined' - ? this.timewindow.history.interval : null - ], - fixedTimewindow: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined' - ? this.timewindow.history.fixedTimewindow : null, - disabled: hideInterval - }), - quickInterval: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.quickInterval !== 'undefined' - ? this.timewindow.history.quickInterval : null, - disabled: hideInterval - }) - } - ), - aggregation: this.fb.group( - { - type: this.fb.control({ - value: this.timewindow.aggregation && typeof this.timewindow.aggregation.type !== 'undefined' - ? this.timewindow.aggregation.type : null, - disabled: hideAggregation - }), - limit: this.fb.control({ - value: this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined' - ? this.checkLimit(this.timewindow.aggregation.limit) : null, - disabled: hideAggInterval - }, []) - } - ), - timezone: this.fb.control({ - value: this.timewindow.timezone !== 'undefined' - ? this.timewindow.timezone : null, - disabled: hideTimezone - }) + realtime: this.fb.group({ + realtimeType: [{ + value: isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, + disabled: hideInterval + }], + timewindowMs: [{ + value: isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null, + disabled: hideInterval || hideLastInterval + }], + interval: [isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null], + quickInterval: [{ + value: isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null, + disabled: hideInterval || hideQuickInterval + }] + }), + history: this.fb.group({ + historyType: [{ + value: isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL, + disabled: hideInterval + }], + timewindowMs: [{ + value: isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null, + disabled: hideInterval + }], + interval: [ isDefined(history?.interval) ? this.timewindow.history.interval : null + ], + fixedTimewindow: [{ + value: isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null, + disabled: hideInterval + }], + quickInterval: [{ + value: isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null, + disabled: hideInterval + }] + }), + aggregation: this.fb.group({ + type: [{ + value: isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null, + disabled: hideAggregation + }], + limit: [{ + value: isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null, + disabled: hideAggInterval + }, []] + }), + timezone: [{ + value: isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null, + disabled: hideTimezone + }] }); this.updateValidators(); this.timewindowForm.get('aggregation.type').valueChanges.subscribe(() => { diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index ca1a4b1f5f..2b689831a9 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -1,6 +1,6 @@ - -
    +
    {{innerValue?.displayValue}} | {{innerValue.displayTimezoneAbbr}} diff --git a/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.scss b/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.scss index 06b464db27..8470316f0b 100644 --- a/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.scss +++ b/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -.mat-option.branch-option { +.mat-mdc-option.branch-option { .mat-icon, .check-placeholder { margin-right: 8px; } @@ -21,7 +21,7 @@ width: 18px; display: inline-block; } - .mat-option-text { + .mdc-list-item__primary-text { width: 100%; .default-branch { float: right; diff --git a/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts b/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts index c135b0a8f7..1b87c9ca3d 100644 --- a/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -35,6 +35,7 @@ import { BranchInfo } from '@shared/models/vc.models'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { isNotEmptyStr } from '@core/utils'; import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { SubscriptSizing } from '@angular/material/form-field'; @Component({ selector: 'tb-branch-autocomplete', @@ -49,10 +50,13 @@ import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autoc }) export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit { - branchFormGroup: FormGroup; + branchFormGroup: UntypedFormGroup; modelValue: string | null; + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + private requiredValue: boolean; get required(): boolean { @@ -91,7 +95,7 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit @ViewChild('branchAutocomplete') matAutocomplete: MatAutocomplete; @ViewChild('branchInput', { read: MatAutocompleteTrigger, static: true }) autoCompleteTrigger: MatAutocompleteTrigger; - @ViewChild('branchInput', {static: true}) branchInput: ElementRef; + @ViewChild('branchInput', {static: true}) branchInput: ElementRef; filteredBranches: Observable>; @@ -109,7 +113,7 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit constructor(private store: Store, private entitiesVersionControlService: EntitiesVersionControlService, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private zone: NgZone) { this.branchFormGroup = this.fb.group({ branch: [null, []] @@ -264,9 +268,7 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit this.searchText = searchText; return this.getBranches().pipe( map(branches => { - let res = branches.filter(branch => { - return searchText ? branch.name.toUpperCase().startsWith(searchText.toUpperCase()) : true; - }); + let res = branches.filter(branch => searchText ? branch.name.toUpperCase().startsWith(searchText.toUpperCase()) : true); if (!this.selectionMode && isNotEmptyStr(searchText) && !res.find(b => b.name === searchText)) { res = [{name: searchText, default: false}, ...res]; } diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html index 0db97bdf23..4790d7e93b 100644 --- a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html +++ b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html @@ -1,6 +1,6 @@ - - + (target: any, key: string): void => { + const getter = function() { + return this['__' + key]; + }; + + const setter = function(next: any) { + this['__' + key] = coerceBooleanProperty(next); + }; + + Object.defineProperty(target, key, { + get: getter, + set: setter, + enumerable: true, + configurable: true, + }); +}; diff --git a/ui-ngx/src/app/shared/decorators/enumerable.ts b/ui-ngx/src/app/shared/decorators/enumerable.ts index 0c66a3bc0d..1e89ff0ca3 100644 --- a/ui-ngx/src/app/shared/decorators/enumerable.ts +++ b/ui-ngx/src/app/shared/decorators/enumerable.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/decorators/tb-inject.ts b/ui-ngx/src/app/shared/decorators/tb-inject.ts index 2c5150600f..6a082b9b63 100644 --- a/ui-ngx/src/app/shared/decorators/tb-inject.ts +++ b/ui-ngx/src/app/shared/decorators/tb-inject.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/ace/ace.models.ts b/ui-ngx/src/app/shared/models/ace/ace.models.ts index 9777935922..5b37c7c2c3 100644 --- a/ui-ngx/src/app/shared/models/ace/ace.models.ts +++ b/ui-ngx/src/app/shared/models/ace/ace.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/ace/completion.models.ts b/ui-ngx/src/app/shared/models/ace/completion.models.ts index 0941fcbe39..c2b8013d63 100644 --- a/ui-ngx/src/app/shared/models/ace/completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/completion.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts index bcad0a000c..c96dc0d98f 100644 --- a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts index 7112283b36..b9d44c64cf 100644 --- a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index fd2e200090..a176d2a69a 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -23,6 +23,13 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { EntityType } from '@shared/models/entity-type.models'; import { CustomerId } from '@shared/models/id/customer-id'; import { TableCellButtonActionDescriptor } from '@home/components/widget/lib/table-widget.models'; +import { AlarmCommentId } from '@shared/models/id/alarm-comment-id'; +import { UserId } from '@shared/models/id/user-id'; + +export enum AlarmsMode { + ALL, + ENTITY +} export enum AlarmSeverity { CRITICAL = 'CRITICAL', @@ -89,6 +96,7 @@ export const alarmSeverityColors = new Map( export interface Alarm extends BaseData { tenantId: TenantId; customerId: CustomerId; + assigneeId: UserId; type: string; originator: EntityId; severity: AlarmSeverity; @@ -97,12 +105,43 @@ export interface Alarm extends BaseData { endTs: number; ackTs: number; clearTs: number; + assignTs: number; propagate: boolean; details?: any; } +export enum AlarmCommentType { + SYSTEM = 'SYSTEM', + OTHER = 'OTHER' +} + +export interface AlarmComment extends BaseData { + alarmId: AlarmId; + userId?: UserId; + type: AlarmCommentType; + comment: { + text: string; + edited?: boolean; + editedOn?: number; + } +} + +export interface AlarmCommentInfo extends AlarmComment { + firstName?: string; + lastName?: string; + email?: string; +} + export interface AlarmInfo extends Alarm { originatorName: string; + originatorLabel: string; + assignee: AlarmAssignee; +} + +export interface AlarmAssignee { + firstName: string; + lastName: string; + email: string; } export interface AlarmDataInfo extends AlarmInfo { @@ -115,12 +154,20 @@ export const simulatedAlarm: AlarmInfo = { id: new AlarmId(NULL_UUID), tenantId: new TenantId(NULL_UUID), customerId: new CustomerId(NULL_UUID), + assigneeId: new UserId(NULL_UUID), createdTime: new Date().getTime(), startTs: new Date().getTime(), endTs: 0, ackTs: 0, clearTs: 0, + assignTs: 0, originatorName: 'Simulated', + originatorLabel: 'Simulated', + assignee: { + firstName: "", + lastName: "", + email: "test@example.com", + }, originator: { entityType: EntityType.DEVICE, id: '1' @@ -172,11 +219,22 @@ export const alarmFields: {[fieldName: string]: AlarmField} = { name: 'alarm.clear-time', time: true }, + assignTime: { + keyName: 'assignTime', + value: 'assignTs', + name: 'alarm.assign-time', + time: true + }, originator: { keyName: 'originator', value: 'originatorName', name: 'alarm.originator' }, + originatorLabel: { + keyName: 'originatorLabel', + value: 'originatorLabel', + name: 'alarm.originator-label' + }, originatorType: { keyName: 'originatorType', value: 'originator.entityType', @@ -196,6 +254,11 @@ export const alarmFields: {[fieldName: string]: AlarmField} = { keyName: 'status', value: 'status', name: 'alarm.status' + }, + assignee: { + keyName: 'assignee', + value: 'assignee', + name: 'alarm.assignee' } }; @@ -206,19 +269,21 @@ export class AlarmQuery { searchStatus: AlarmSearchStatus; status: AlarmStatus; fetchOriginator: boolean; + assigneeId?: UserId; constructor(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, - fetchOriginator: boolean) { + fetchOriginator: boolean, assigneeId?: UserId) { this.affectedEntityId = entityId; this.pageLink = pageLink; this.searchStatus = searchStatus; this.status = status; this.fetchOriginator = fetchOriginator; + this.assigneeId = assigneeId; } public toQuery(): string { - let query = `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}`; + let query = this.affectedEntityId ? `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}` : ''; query += this.pageLink.toQuery(); if (this.searchStatus) { query += `&searchStatus=${this.searchStatus}`; @@ -228,6 +293,9 @@ export class AlarmQuery { if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) { query += `&fetchOriginator=${this.fetchOriginator}`; } + if (typeof this.assigneeId !== 'undefined' && this.assigneeId !== null) { + query += `&assigneeId=${this.assigneeId.id}`; + } return query; } diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index f67c0d50a7..fdcc3407a6 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -127,7 +127,7 @@ export interface EntitySearchQueryFilter { fetchLastLevelOnly?: boolean; } -// tslint:disable-next-line:no-empty-interface +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ApiUsageStateFilter { } diff --git a/ui-ngx/src/app/shared/models/api-usage.models.ts b/ui-ngx/src/app/shared/models/api-usage.models.ts new file mode 100644 index 0000000000..bbc0ff5597 --- /dev/null +++ b/ui-ngx/src/app/shared/models/api-usage.models.ts @@ -0,0 +1,47 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +export enum ApiUsageStateValue { + ENABLED = 'ENABLED', + WARNING = 'WARNING', + DISABLED = 'DISABLED' +} + +export const ApiUsageStateValueTranslationMap = new Map([ + [ApiUsageStateValue.ENABLED, 'notification.enabled'], + [ApiUsageStateValue.WARNING, 'notification.warning'], + [ApiUsageStateValue.DISABLED, 'notification.disabled'], +]); + +export enum ApiFeature { + TRANSPORT = 'TRANSPORT', + DB = 'DB', + RE = 'RE', + JS = 'JS', + EMAIL = 'EMAIL', + SMS = 'SMS', + ALARM = 'ALARM' +} + +export const ApiFeatureTranslationMap = new Map([ + [ApiFeature.TRANSPORT, 'api-usage.device-api'], + [ApiFeature.DB, 'api-usage.telemetry-persistence'], + [ApiFeature.RE, 'api-usage.rule-engine-executions'], + [ApiFeature.JS, 'api-usage.javascript-executions'], + [ApiFeature.EMAIL, 'api-usage.email-messages'], + [ApiFeature.SMS, 'api-usage.sms-messages'], + [ApiFeature.ALARM, 'api-usage.alarm'], +]); diff --git a/ui-ngx/src/app/shared/models/asset.models.ts b/ui-ngx/src/app/shared/models/asset.models.ts index 029c560310..a1ab16fdc8 100644 --- a/ui-ngx/src/app/shared/models/asset.models.ts +++ b/ui-ngx/src/app/shared/models/asset.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -35,9 +35,11 @@ export interface AssetProfile extends BaseData, ExportableEntity defaultRuleChainId?: RuleChainId; defaultDashboardId?: DashboardId; defaultQueueName?: string; + defaultEdgeRuleChainId?: RuleChainId; } export interface AssetProfileInfo extends EntityInfoData { + tenantId?: TenantId; image?: string; defaultDashboardId?: DashboardId; } diff --git a/ui-ngx/src/app/shared/models/audit-log.models.ts b/ui-ngx/src/app/shared/models/audit-log.models.ts index 056c6aa569..8f4e26171e 100644 --- a/ui-ngx/src/app/shared/models/audit-log.models.ts +++ b/ui-ngx/src/app/shared/models/audit-log.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -47,6 +47,11 @@ export enum ActionType { RELATIONS_DELETED = 'RELATIONS_DELETED', ALARM_ACK = 'ALARM_ACK', ALARM_CLEAR = 'ALARM_CLEAR', + ALARM_ASSIGN = 'ALARM_ASSIGN', + ALARM_UNASSIGN = 'ALARM_UNASSIGN', + ADDED_COMMENT = 'ADDED_COMMENT', + UPDATED_COMMENT = 'UPDATED_COMMENT', + DELETED_COMMENT = 'DELETED_COMMENT', LOGIN = 'LOGIN', LOGOUT = 'LOGOUT', LOCKOUT = 'LOCKOUT', @@ -85,6 +90,11 @@ export const actionTypeTranslations = new Map( [ActionType.RELATIONS_DELETED, 'audit-log.type-relations-delete'], [ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'], [ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'], + [ActionType.ALARM_ASSIGN, 'audit-log.type-alarm-assign'], + [ActionType.ALARM_UNASSIGN, 'audit-log.type-alarm-unassign'], + [ActionType.ADDED_COMMENT, 'audit-log.type-added-comment'], + [ActionType.UPDATED_COMMENT, 'audit-log.type-updated-comment'], + [ActionType.DELETED_COMMENT, 'audit-log.type-deleted-comment'], [ActionType.LOGIN, 'audit-log.type-login'], [ActionType.LOGOUT, 'audit-log.type-logout'], [ActionType.LOCKOUT, 'audit-log.type-lockout'], diff --git a/ui-ngx/src/app/shared/models/authority.enum.ts b/ui-ngx/src/app/shared/models/authority.enum.ts index 683c153b07..c736fe9cb6 100644 --- a/ui-ngx/src/app/shared/models/authority.enum.ts +++ b/ui-ngx/src/app/shared/models/authority.enum.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/base-data.ts b/ui-ngx/src/app/shared/models/base-data.ts index 7b3ea9a642..16022641aa 100644 --- a/ui-ngx/src/app/shared/models/base-data.ts +++ b/ui-ngx/src/app/shared/models/base-data.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -27,6 +27,17 @@ export interface BaseData { label?: string; } +export function sortEntitiesByIds>(entities: T[], entityIds: string[]): T[] { + entities.sort((entity1, entity2) => { + const id1 = entity1.id.id; + const id2 = entity2.id.id; + const index1 = entityIds.indexOf(id1); + const index2 = entityIds.indexOf(id2); + return index1 - index2; + }); + return entities; +} + export interface ExportableEntity { createdTime?: number; id?: T; diff --git a/ui-ngx/src/app/shared/models/beautify.models.ts b/ui-ngx/src/app/shared/models/beautify.models.ts index 6e2affc6fe..ad7c6427c0 100644 --- a/ui-ngx/src/app/shared/models/beautify.models.ts +++ b/ui-ngx/src/app/shared/models/beautify.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/component-descriptor.models.ts b/ui-ngx/src/app/shared/models/component-descriptor.models.ts index 47e7d5af42..56dd29cb4e 100644 --- a/ui-ngx/src/app/shared/models/component-descriptor.models.ts +++ b/ui-ngx/src/app/shared/models/component-descriptor.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 6d2a098ca6..a93df2e3fd 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -76,6 +76,7 @@ export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', smsProviderSettings: helpBaseUrl + '/docs/user-guide/ui/sms-provider-settings', + slackSettings: helpBaseUrl + '/docs/user-guide/ui/slack-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', twoFactorAuthSettings: helpBaseUrl + '/docs/', @@ -85,6 +86,9 @@ export const HelpLinks = { ruleNodeGpsGeofencingFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#gps-geofencing-filter-node', ruleNodeJsFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#script-filter-node', ruleNodeJsSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#switch-node', + ruleNodeAssetProfileSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#asset-profile-switch', + ruleNodeDeviceProfileSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#device-profile-switch', + ruleNodeCheckAlarmStatus: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-alarm-status', ruleNodeMessageTypeFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-filter-node', ruleNodeMessageTypeSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-switch-node', ruleNodeOriginatorTypeFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-filter-node', diff --git a/ui-ngx/src/app/shared/models/contact-based.model.ts b/ui-ngx/src/app/shared/models/contact-based.model.ts index f39f1fa290..d7da687098 100644 --- a/ui-ngx/src/app/shared/models/contact-based.model.ts +++ b/ui-ngx/src/app/shared/models/contact-based.model.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/country.models.ts b/ui-ngx/src/app/shared/models/country.models.ts index afba058c6d..f243ba3a5c 100644 --- a/ui-ngx/src/app/shared/models/country.models.ts +++ b/ui-ngx/src/app/shared/models/country.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts index 67c1429189..d26725814f 100644 --- a/ui-ngx/src/app/shared/models/customer.model.ts +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index bf71997e35..8548bc73d2 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index d06963544e..ae601b0039 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -242,6 +242,7 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; + sparkplug?: boolean; sendAckOnValidationException?: boolean; transportPayloadTypeConfiguration?: { transportPayloadType?: TransportPayloadType; @@ -359,6 +360,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', + sparkplug: false, sendAckOnValidationException: false, transportPayloadTypeConfiguration: { transportPayloadType: TransportPayloadType.JSON, @@ -498,13 +500,15 @@ export interface CustomTimeSchedulerItem{ endsOn: number; } -export interface AlarmRule { +interface AlarmRule { condition: AlarmCondition; alarmDetails?: string; dashboardId?: DashboardId; schedule?: AlarmSchedule; } +export { AlarmRule as DeviceProfileAlarmRule }; + export function alarmRuleValidator(control: AbstractControl): ValidationErrors | null { const alarmRule: AlarmRule = control.value; return alarmRuleValid(alarmRule) ? null : {alarmRule: true}; @@ -579,9 +583,11 @@ export interface DeviceProfile extends BaseData, ExportableEnti firmwareId?: OtaPackageId; softwareId?: OtaPackageId; profileData: DeviceProfileData; + defaultEdgeRuleChainId?: RuleChainId; } export interface DeviceProfileInfo extends EntityInfoData { + tenantId?: TenantId; type: DeviceProfileType; transportType: DeviceTransportType; image?: string; diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 59f9d54318..372a910402 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -167,3 +167,7 @@ export interface EdgeEvent extends BaseData { uid: string; body: string; } + +export interface EdgeInstallInstructions { + dockerInstallInstructions: string; +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 83a7ebebf8..033fe363c8 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -38,7 +38,12 @@ export enum EntityType { TB_RESOURCE = 'TB_RESOURCE', OTA_PACKAGE = 'OTA_PACKAGE', RPC = 'RPC', - QUEUE = 'QUEUE' + QUEUE = 'QUEUE', + NOTIFICATION = 'NOTIFICATION', + NOTIFICATION_REQUEST = 'NOTIFICATION_REQUEST', + NOTIFICATION_RULE = 'NOTIFICATION_RULE', + NOTIFICATION_TARGET = 'NOTIFICATION_TARGET', + NOTIFICATION_TEMPLATE = 'NOTIFICATION_TEMPLATE' } export enum AliasEntityType { @@ -251,7 +256,7 @@ export const entityTypeTranslations = new Map([ [EntityType.CUSTOMER, '/customers'], [EntityType.USER, '/users'], [EntityType.DASHBOARD, '/dashboards'], - [EntityType.ASSET, '/assets'], - [EntityType.DEVICE, '/devices'], + [EntityType.ASSET, '/entities/assets'], + [EntityType.DEVICE, '/entities/devices'], [EntityType.DEVICE_PROFILE, '/profiles/deviceProfiles'], [EntityType.ASSET_PROFILE, '/profiles/assetProfiles'], [EntityType.RULE_CHAIN, '/ruleChains'], - [EntityType.EDGE, '/edgeInstances'], - [EntityType.ENTITY_VIEW, '/entityViews'], - [EntityType.TB_RESOURCE, '/settings/resources-library'], - [EntityType.OTA_PACKAGE, '/otaUpdates'], + [EntityType.EDGE, '/edgeManagement/instances'], + [EntityType.ENTITY_VIEW, '/entities/entityViews'], + [EntityType.TB_RESOURCE, '/resources/resources-library'], + [EntityType.OTA_PACKAGE, '/features/otaUpdates'], [EntityType.QUEUE, '/settings/queues'] ]); diff --git a/ui-ngx/src/app/shared/models/entity-view.models.ts b/ui-ngx/src/app/shared/models/entity-view.models.ts index 9605edfc85..dd16914bc1 100644 --- a/ui-ngx/src/app/shared/models/entity-view.models.ts +++ b/ui-ngx/src/app/shared/models/entity-view.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/entity.models.ts b/ui-ngx/src/app/shared/models/entity.models.ts index 8bcfa6c34a..548b912b04 100644 --- a/ui-ngx/src/app/shared/models/entity.models.ts +++ b/ui-ngx/src/app/shared/models/entity.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/error.models.ts b/ui-ngx/src/app/shared/models/error.models.ts index ddc0b676ce..794d8f6891 100644 --- a/ui-ngx/src/app/shared/models/error.models.ts +++ b/ui-ngx/src/app/shared/models/error.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/event.models.ts b/ui-ngx/src/app/shared/models/event.models.ts index 89e93d4fa1..9664c88fcf 100644 --- a/ui-ngx/src/app/shared/models/event.models.ts +++ b/ui-ngx/src/app/shared/models/event.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts b/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts new file mode 100644 index 0000000000..c161e3095c --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts @@ -0,0 +1,24 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { HasUUID } from '@shared/models/id/has-uuid'; + +export class AlarmCommentId implements HasUUID { + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/alarm-id.ts b/ui-ngx/src/app/shared/models/id/alarm-id.ts index 95d0deb18f..3cd21f1157 100644 --- a/ui-ngx/src/app/shared/models/id/alarm-id.ts +++ b/ui-ngx/src/app/shared/models/id/alarm-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/asset-id.ts b/ui-ngx/src/app/shared/models/id/asset-id.ts index 2a8a911428..9adf209128 100644 --- a/ui-ngx/src/app/shared/models/id/asset-id.ts +++ b/ui-ngx/src/app/shared/models/id/asset-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/asset-profile-id.ts b/ui-ngx/src/app/shared/models/id/asset-profile-id.ts index 8fc021a16f..55d36db2fa 100644 --- a/ui-ngx/src/app/shared/models/id/asset-profile-id.ts +++ b/ui-ngx/src/app/shared/models/id/asset-profile-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/audit-log-id.ts b/ui-ngx/src/app/shared/models/id/audit-log-id.ts index ba5e022dbf..bccaa6ed17 100644 --- a/ui-ngx/src/app/shared/models/id/audit-log-id.ts +++ b/ui-ngx/src/app/shared/models/id/audit-log-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/customer-id.ts b/ui-ngx/src/app/shared/models/id/customer-id.ts index e5a6a49601..8e55155c53 100644 --- a/ui-ngx/src/app/shared/models/id/customer-id.ts +++ b/ui-ngx/src/app/shared/models/id/customer-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/dashboard-id.ts b/ui-ngx/src/app/shared/models/id/dashboard-id.ts index 54ae24c2d5..a83900193d 100644 --- a/ui-ngx/src/app/shared/models/id/dashboard-id.ts +++ b/ui-ngx/src/app/shared/models/id/dashboard-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/device-credentials-id.ts b/ui-ngx/src/app/shared/models/id/device-credentials-id.ts index 1f428c0be8..21b73659a6 100644 --- a/ui-ngx/src/app/shared/models/id/device-credentials-id.ts +++ b/ui-ngx/src/app/shared/models/id/device-credentials-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/device-id.ts b/ui-ngx/src/app/shared/models/id/device-id.ts index 2432215133..71cc69de5a 100644 --- a/ui-ngx/src/app/shared/models/id/device-id.ts +++ b/ui-ngx/src/app/shared/models/id/device-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/device-profile-id.ts b/ui-ngx/src/app/shared/models/id/device-profile-id.ts index a50def52ad..a9596461eb 100644 --- a/ui-ngx/src/app/shared/models/id/device-profile-id.ts +++ b/ui-ngx/src/app/shared/models/id/device-profile-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/edge-id.ts b/ui-ngx/src/app/shared/models/id/edge-id.ts index ef21c384e7..5d26c14fee 100644 --- a/ui-ngx/src/app/shared/models/id/edge-id.ts +++ b/ui-ngx/src/app/shared/models/id/edge-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/entity-id.ts b/ui-ngx/src/app/shared/models/id/entity-id.ts index b208de441b..dc71119cbc 100644 --- a/ui-ngx/src/app/shared/models/id/entity-id.ts +++ b/ui-ngx/src/app/shared/models/id/entity-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/entity-view-id.ts b/ui-ngx/src/app/shared/models/id/entity-view-id.ts index d48f29955f..4ee80a8790 100644 --- a/ui-ngx/src/app/shared/models/id/entity-view-id.ts +++ b/ui-ngx/src/app/shared/models/id/entity-view-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/event-id.ts b/ui-ngx/src/app/shared/models/id/event-id.ts index 9d84b95a91..af15972856 100644 --- a/ui-ngx/src/app/shared/models/id/event-id.ts +++ b/ui-ngx/src/app/shared/models/id/event-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/has-uuid.ts b/ui-ngx/src/app/shared/models/id/has-uuid.ts index 2c6cb83715..e53f008c1f 100644 --- a/ui-ngx/src/app/shared/models/id/has-uuid.ts +++ b/ui-ngx/src/app/shared/models/id/has-uuid.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/notification-id.ts b/ui-ngx/src/app/shared/models/id/notification-id.ts new file mode 100644 index 0000000000..a3286342bb --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/notification-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class NotificationId implements EntityId { + entityType = EntityType.NOTIFICATION; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/notification-request-id.ts b/ui-ngx/src/app/shared/models/id/notification-request-id.ts new file mode 100644 index 0000000000..518ccc884d --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/notification-request-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class NotificationRequestId implements EntityId { + entityType = EntityType.NOTIFICATION_REQUEST; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/notification-rule-id.ts b/ui-ngx/src/app/shared/models/id/notification-rule-id.ts new file mode 100644 index 0000000000..7c1941db65 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/notification-rule-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class NotificationRuleId implements EntityId { + entityType = EntityType.NOTIFICATION_RULE; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/notification-target-id.ts b/ui-ngx/src/app/shared/models/id/notification-target-id.ts new file mode 100644 index 0000000000..ad0ac93f4f --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/notification-target-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class NotificationTargetId implements EntityId { + entityType = EntityType.NOTIFICATION_TARGET; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/notification-template-id.ts b/ui-ngx/src/app/shared/models/id/notification-template-id.ts new file mode 100644 index 0000000000..be93db5d21 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/notification-template-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class NotificationTemplateId implements EntityId { + entityType = EntityType.NOTIFICATION_TEMPLATE; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/ota-package-id.ts b/ui-ngx/src/app/shared/models/id/ota-package-id.ts index 41b3f9b7bb..1bf241ee9a 100644 --- a/ui-ngx/src/app/shared/models/id/ota-package-id.ts +++ b/ui-ngx/src/app/shared/models/id/ota-package-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/public-api.ts b/ui-ngx/src/app/shared/models/id/public-api.ts index f30a5ffbcf..fd6e5854d8 100644 --- a/ui-ngx/src/app/shared/models/id/public-api.ts +++ b/ui-ngx/src/app/shared/models/id/public-api.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -26,6 +26,11 @@ export * from './entity-id'; export * from './entity-view-id'; export * from './event-id'; export * from './has-uuid'; +export * from './notification-id'; +export * from './notification-request-id'; +export * from './notification-rule-id'; +export * from './notification-target-id'; +export * from './notification-template-id'; export * from './ota-package-id'; export * from './rpc-id'; export * from './rule-chain-id'; diff --git a/ui-ngx/src/app/shared/models/id/queue-id.ts b/ui-ngx/src/app/shared/models/id/queue-id.ts index b36a12547b..743583e32e 100644 --- a/ui-ngx/src/app/shared/models/id/queue-id.ts +++ b/ui-ngx/src/app/shared/models/id/queue-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/rpc-id.ts b/ui-ngx/src/app/shared/models/id/rpc-id.ts index a3b9cfec91..7173237ac0 100644 --- a/ui-ngx/src/app/shared/models/id/rpc-id.ts +++ b/ui-ngx/src/app/shared/models/id/rpc-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/rule-chain-id.ts b/ui-ngx/src/app/shared/models/id/rule-chain-id.ts index ebcc47d415..27623bd15a 100644 --- a/ui-ngx/src/app/shared/models/id/rule-chain-id.ts +++ b/ui-ngx/src/app/shared/models/id/rule-chain-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/rule-node-id.ts b/ui-ngx/src/app/shared/models/id/rule-node-id.ts index 68ebfc2925..73d57f68f2 100644 --- a/ui-ngx/src/app/shared/models/id/rule-node-id.ts +++ b/ui-ngx/src/app/shared/models/id/rule-node-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/tb-resource-id.ts b/ui-ngx/src/app/shared/models/id/tb-resource-id.ts index f36aa36890..57376181e0 100644 --- a/ui-ngx/src/app/shared/models/id/tb-resource-id.ts +++ b/ui-ngx/src/app/shared/models/id/tb-resource-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/tenant-id.ts b/ui-ngx/src/app/shared/models/id/tenant-id.ts index 078dd0b399..59864eb9d9 100644 --- a/ui-ngx/src/app/shared/models/id/tenant-id.ts +++ b/ui-ngx/src/app/shared/models/id/tenant-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/tenant-profile-id.ts b/ui-ngx/src/app/shared/models/id/tenant-profile-id.ts index 0cef7d7d13..a829b4e82c 100644 --- a/ui-ngx/src/app/shared/models/id/tenant-profile-id.ts +++ b/ui-ngx/src/app/shared/models/id/tenant-profile-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/user-id.ts b/ui-ngx/src/app/shared/models/id/user-id.ts index 392378cd66..59c46be97f 100644 --- a/ui-ngx/src/app/shared/models/id/user-id.ts +++ b/ui-ngx/src/app/shared/models/id/user-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/widget-type-id.ts b/ui-ngx/src/app/shared/models/id/widget-type-id.ts index 38575843be..298ef70ae1 100644 --- a/ui-ngx/src/app/shared/models/id/widget-type-id.ts +++ b/ui-ngx/src/app/shared/models/id/widget-type-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts b/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts index 9b6f7cee44..fc16726820 100644 --- a/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts +++ b/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/login.models.ts b/ui-ngx/src/app/shared/models/login.models.ts index d3f1598fc5..0da429f239 100644 --- a/ui-ngx/src/app/shared/models/login.models.ts +++ b/ui-ngx/src/app/shared/models/login.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts index 9c6b5cce6a..26cff38c28 100644 --- a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts +++ b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/material.models.ts b/ui-ngx/src/app/shared/models/material.models.ts index 5defa7d044..ea75978f26 100644 --- a/ui-ngx/src/app/shared/models/material.models.ts +++ b/ui-ngx/src/app/shared/models/material.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts new file mode 100644 index 0000000000..0969af78d4 --- /dev/null +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -0,0 +1,567 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NotificationId } from '@shared/models/id/notification-id'; +import { NotificationRequestId } from '@shared/models/id/notification-request-id'; +import { UserId } from '@shared/models/id/user-id'; +import { BaseData } from '@shared/models/base-data'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { NotificationTargetId } from '@shared/models/id/notification-target-id'; +import { NotificationTemplateId } from '@shared/models/id/notification-template-id'; +import { EntityId } from '@shared/models/id/entity-id'; +import { NotificationRuleId } from '@shared/models/id/notification-rule-id'; +import { AlarmSearchStatus, AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { User } from '@shared/models/user.model'; +import { ApiFeature, ApiUsageStateValue } from '@shared/models/api-usage.models'; + +export interface Notification { + readonly id: NotificationId; + readonly requestId: NotificationRequestId; + readonly recipientId: UserId; + readonly type: NotificationType; + readonly subject: string; + readonly text: string; + readonly info: NotificationInfo; + readonly status: NotificationStatus; + readonly createdTime: number; + readonly additionalConfig?: WebDeliveryMethodAdditionalConfig; +} + +export interface NotificationInfo { + description: string; + type: string; + alarmSeverity?: AlarmSeverity; + alarmStatus?: AlarmStatus; + alarmType?: string; + stateEntityId?: EntityId; +} + +export interface NotificationRequest extends Omit, 'label'> { + tenantId?: TenantId; + targets: Array; + templateId?: NotificationTemplateId; + template?: NotificationTemplate; + info?: NotificationInfo; + deliveryMethods: Array; + originatorEntityId: EntityId; + status: NotificationRequestStatus; + stats: NotificationRequestStats; + additionalConfig: NotificationRequestConfig; +} + +export interface NotificationRequestInfo extends NotificationRequest { + templateName: string; + deliveryMethods: NotificationDeliveryMethod[]; +} + +export interface NotificationRequestPreview { + totalRecipientsCount: number; + recipientsCountByTarget: { [key in string]: number }; + processedTemplates: { [key in NotificationDeliveryMethod]: DeliveryMethodNotificationTemplate }; + recipientsPreview: Array; +} + +export interface NotificationRequestStats { + sent: Map; + errors: { [key in NotificationDeliveryMethod]: {[errorKey in string]: string}}; + processedRecipients: Map>; +} + +export interface NotificationRequestConfig { + sendingDelayInSec: number; +} + +export interface NotificationSettings { + deliveryMethodsConfigs: { [key in NotificationDeliveryMethod]: NotificationDeliveryMethodConfig }; +} + +export interface NotificationDeliveryMethodConfig extends Partial{ + enabled: boolean; + method: NotificationDeliveryMethod; +} + +interface SlackNotificationDeliveryMethodConfig { + botToken: string; +} + +export interface SlackConversation { + id: string; + title: string; + shortName: string; + wholeName: string; + email: string; +} + +export interface NotificationRule extends Omit, 'label'>{ + tenantId: TenantId; + templateId: NotificationTemplateId; + triggerType: TriggerType; + triggerConfig: NotificationRuleTriggerConfig; + recipientsConfig: NotificationRuleRecipientConfig; + additionalConfig: {description: string}; +} + +export type NotificationRuleTriggerConfig = Partial; + +export interface AlarmNotificationRuleTriggerConfig { + alarmTypes?: Array; + alarmSeverities?: Array; + notifyOn: Array; + clearRule?: ClearRule; +} + +interface ClearRule { + alarmStatuses: Array; +} + +export interface DeviceInactivityNotificationRuleTriggerConfig { + devices?: Array; + deviceProfiles?: Array; +} + +export interface EntityActionNotificationRuleTriggerConfig { + entityTypes: EntityType[]; + created: boolean; + updated: boolean; + deleted: boolean; +} + +export interface AlarmCommentNotificationRuleTriggerConfig { + alarmTypes?: Array; + alarmSeverities?: Array; + alarmStatuses?: Array; + onlyUserComments?: boolean; + notifyOnCommentUpdate?: boolean; +} + +export interface AlarmAssignmentNotificationRuleTriggerConfig { + alarmTypes?: Array; + alarmSeverities?: Array; + alarmStatuses?: Array; + notifyOn: Array; +} + +export interface RuleEngineLifecycleEventNotificationRuleTriggerConfig { + ruleChains?: Array; + ruleChainEvents?: Array; + onlyRuleChainLifecycleFailures: boolean; + trackRuleNodeEvents: boolean; + ruleNodeEvents: Array; + onlyRuleNodeLifecycleFailures: ComponentLifecycleEvent; +} + +export interface EntitiesLimitNotificationRuleTriggerConfig { + entityTypes: EntityType[]; + threshold: number; +} + +export interface ApiUsageLimitNotificationRuleTriggerConfig { + apiFeatures: ApiFeature[]; + notifyOn: ApiUsageStateValue[]; +} + +export enum ComponentLifecycleEvent { + STARTED = 'STARTED', + UPDATED = 'UPDATED', + STOPPED = 'STOPPED' +} + +export const ComponentLifecycleEventTranslationMap = new Map([ + [ComponentLifecycleEvent.STARTED, 'event.started'], + [ComponentLifecycleEvent.UPDATED, 'event.updated'], + [ComponentLifecycleEvent.STOPPED, 'event.stopped'] +]); + +export enum AlarmAction { + CREATED = 'CREATED', + SEVERITY_CHANGED = 'SEVERITY_CHANGED', + ACKNOWLEDGED = 'ACKNOWLEDGED', + CLEARED = 'CLEARED' +} + +export const AlarmActionTranslationMap = new Map([ + [AlarmAction.CREATED, 'notification.notify-alarm-action.created'], + [AlarmAction.SEVERITY_CHANGED, 'notification.notify-alarm-action.severity-changed'], + [AlarmAction.ACKNOWLEDGED, 'notification.notify-alarm-action.acknowledged'], + [AlarmAction.CLEARED, 'notification.notify-alarm-action.cleared'] +]); + +export enum AlarmAssignmentAction { + ASSIGNED = 'ASSIGNED', + UNASSIGNED = 'UNASSIGNED' +} + +export const AlarmAssignmentActionTranslationMap = new Map([ + [AlarmAssignmentAction.ASSIGNED, 'notification.notify-alarm-action.assigned'], + [AlarmAssignmentAction.UNASSIGNED, 'notification.notify-alarm-action.unassigned'] +]); + +export enum DeviceEvent { + ACTIVE = 'ACTIVE', + INACTIVE = 'INACTIVE' +} + +export const DeviceEventTranslationMap = new Map([ + [DeviceEvent.ACTIVE, 'notification.active'], + [DeviceEvent.INACTIVE, 'notification.inactive'] +]); + +export interface NotificationRuleRecipientConfig { + targets?: Array; + escalationTable?: {[key: number]: Array}; +} + +export interface NonConfirmedNotificationEscalation { + delayInSec: number; + targets: Array; +} + +export interface NotificationTarget extends Omit, 'label'>{ + tenantId: TenantId; + configuration: NotificationTargetConfig; +} + +export interface NotificationTargetConfig extends Partial { + description?: string; + type: NotificationTargetType; +} +export interface PlatformUsersNotificationTargetConfig { + usersFilter: UsersFilter; +} + +export interface UsersFilter extends + Partial{ + type: NotificationTargetConfigType; +} + +interface UserListFilter { + usersIds: Array; +} + +interface CustomerUsersFilter { + customerId: string; +} + +interface TenantAdministratorsFilter { + tenantsIds?: Array; + tenantProfilesIds?: Array; +} + +export interface SlackNotificationTargetConfig { + conversationType: SlackChanelType; + conversation: SlackConversation; +} +export enum NotificationTargetType { + PLATFORM_USERS = 'PLATFORM_USERS', + SLACK = 'SLACK' +} + +export const NotificationTargetTypeTranslationMap = new Map([ + [NotificationTargetType.PLATFORM_USERS, 'notification.platform-users'], + [NotificationTargetType.SLACK, 'notification.delivery-method.slack'] +]); + +export interface NotificationTemplate extends Omit, 'label'>{ + tenantId: TenantId; + notificationType: NotificationType; + configuration: NotificationTemplateConfig; +} + +interface NotificationTemplateConfig { + deliveryMethodsTemplates: { + [key in NotificationDeliveryMethod]: DeliveryMethodNotificationTemplate + }; +} + +export interface DeliveryMethodNotificationTemplate extends + Partial{ + body?: string; + enabled: boolean; + method: NotificationDeliveryMethod; +} + +interface WebDeliveryMethodNotificationTemplate { + subject?: string; + additionalConfig: WebDeliveryMethodAdditionalConfig; +} + +interface WebDeliveryMethodAdditionalConfig { + icon: { + enabled: boolean; + icon: string; + color: string; + }; + actionButtonConfig: { + enabled: boolean; + text: string; + linkType: ActionButtonLinkType; + link?: string; + dashboardId?: string; + dashboardState?: string; + setEntityIdInState?: boolean; + }; +} + +interface EmailDeliveryMethodNotificationTemplate { + subject: string; +} + +interface SlackDeliveryMethodNotificationTemplate { + conversationType: SlackChanelType; + conversationId: string; +} + +export enum NotificationStatus { + SENT = 'SENT', + READ = 'READ' +} + +export enum NotificationDeliveryMethod { + WEB = 'WEB', + SMS = 'SMS', + EMAIL = 'EMAIL', + SLACK = 'SLACK' +} + +export const NotificationDeliveryMethodTranslateMap = new Map([ + [NotificationDeliveryMethod.WEB, 'notification.delivery-method.web'], + [NotificationDeliveryMethod.SMS, 'notification.delivery-method.sms'], + [NotificationDeliveryMethod.EMAIL, 'notification.delivery-method.email'], + [NotificationDeliveryMethod.SLACK, 'notification.delivery-method.slack'] +]); + +export enum NotificationRequestStatus { + PROCESSING = 'PROCESSING', + SCHEDULED = 'SCHEDULED', + SENT = 'SENT' +} + +export const NotificationRequestStatusTranslateMap = new Map([ + [NotificationRequestStatus.PROCESSING, 'notification.request-status.processing'], + [NotificationRequestStatus.SCHEDULED, 'notification.request-status.scheduled'], + [NotificationRequestStatus.SENT, 'notification.request-status.sent'] +]); + +export enum SlackChanelType { + PUBLIC_CHANNEL = 'PUBLIC_CHANNEL', + PRIVATE_CHANNEL = 'PRIVATE_CHANNEL', + DIRECT= 'DIRECT' +} + +export const SlackChanelTypesTranslateMap = new Map([ + [SlackChanelType.DIRECT, 'notification.slack-chanel-types.direct'], + [SlackChanelType.PUBLIC_CHANNEL, 'notification.slack-chanel-types.public-channel'], + [SlackChanelType.PRIVATE_CHANNEL, 'notification.slack-chanel-types.private-channel'] +]); + +export enum NotificationTargetConfigType { + ALL_USERS = 'ALL_USERS', + TENANT_ADMINISTRATORS = 'TENANT_ADMINISTRATORS', + CUSTOMER_USERS = 'CUSTOMER_USERS', + USER_LIST = 'USER_LIST', + ORIGINATOR_ENTITY_OWNER_USERS = 'ORIGINATOR_ENTITY_OWNER_USERS', + AFFECTED_USER = 'AFFECTED_USER', + SYSTEM_ADMINISTRATORS = 'SYSTEM_ADMINISTRATORS', + AFFECTED_TENANT_ADMINISTRATORS = 'AFFECTED_TENANT_ADMINISTRATORS' +} + +export interface NotificationTargetConfigTypeInfo { + name: string; + hint?: string; +} + +export const NotificationTargetConfigTypeInfoMap = new Map([ + [NotificationTargetConfigType.ALL_USERS, + { + name: 'notification.recipient-type.all-users' + } + ], + [NotificationTargetConfigType.TENANT_ADMINISTRATORS, + { + name: 'notification.recipient-type.tenant-administrators' + } + ], + [NotificationTargetConfigType.CUSTOMER_USERS, + { + name: 'notification.recipient-type.customer-users' + } + ], + [NotificationTargetConfigType.USER_LIST, + { + name: 'notification.recipient-type.user-list' + } + ], + [NotificationTargetConfigType.ORIGINATOR_ENTITY_OWNER_USERS, + { + name: 'notification.recipient-type.users-entity-owner', + hint: 'notification.recipient-type.users-entity-owner-hint' + } + ], + [NotificationTargetConfigType.AFFECTED_USER, + { + name: 'notification.recipient-type.affected-user', + hint: 'notification.recipient-type.affected-user-hint' + } + ], + [NotificationTargetConfigType.SYSTEM_ADMINISTRATORS, + { + name: 'notification.recipient-type.system-administrators' + } + ], + [NotificationTargetConfigType.AFFECTED_TENANT_ADMINISTRATORS, + { + name: 'notification.recipient-type.affected-tenant-administrators' + } + ] +]); + +export enum NotificationType { + GENERAL = 'GENERAL', + ALARM = 'ALARM', + DEVICE_ACTIVITY = 'DEVICE_ACTIVITY', + ENTITY_ACTION = 'ENTITY_ACTION', + ALARM_COMMENT = 'ALARM_COMMENT', + ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT', + RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT', + ENTITIES_LIMIT = 'ENTITIES_LIMIT', + API_USAGE_LIMIT = 'API_USAGE_LIMIT', + RULE_ENGINE = 'RULE_ENGINE' +} + +export const NotificationTypeIcons = new Map([ + [NotificationType.ALARM, 'warning'], + [NotificationType.DEVICE_ACTIVITY, 'phonelink_off'], + [NotificationType.ENTITY_ACTION, 'devices'], + [NotificationType.ALARM_COMMENT, 'comment'], + [NotificationType.ALARM_ASSIGNMENT, 'assignment_turned_in'], + [NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'settings_ethernet'], + [NotificationType.ENTITIES_LIMIT, 'data_thresholding'], + [NotificationType.API_USAGE_LIMIT, 'insert_chart'], +]); + +export const AlarmSeverityNotificationColors = new Map( + [ + [AlarmSeverity.CRITICAL, '#D12730'], + [AlarmSeverity.MAJOR, '#FEAC0C'], + [AlarmSeverity.MINOR, '#F2DA05'], + [AlarmSeverity.WARNING, '#F66716'], + [AlarmSeverity.INDETERMINATE, '#00000061'] + ] +); + +export enum ActionButtonLinkType { + LINK = 'LINK', + DASHBOARD = 'DASHBOARD' +} + +export const ActionButtonLinkTypeTranslateMap = new Map([ + [ActionButtonLinkType.LINK, 'notification.link-type.link'], + [ActionButtonLinkType.DASHBOARD, 'notification.link-type.dashboard'] +]); + +export interface NotificationTemplateTypeTranslate { + name: string; + helpId?: string; +} + +export const NotificationTemplateTypeTranslateMap = new Map([ + [NotificationType.GENERAL, + { + name: 'notification.template-type.general', + helpId: 'notification/general' + } + ], + [NotificationType.ALARM, + { + name: 'notification.template-type.alarm', + helpId: 'notification/alarm' + } + ], + [NotificationType.DEVICE_ACTIVITY, + { + name: 'notification.template-type.device-activity', + helpId: 'notification/device_activity' + } + ], + [NotificationType.ENTITY_ACTION, + { + name: 'notification.template-type.entity-action', + helpId: 'notification/entity_action' + } + ], + [NotificationType.ALARM_COMMENT, + { + name: 'notification.template-type.alarm-comment', + helpId: 'notification/alarm_comment' + } + ], + [NotificationType.ALARM_ASSIGNMENT, + { + name: 'notification.template-type.alarm-assignment', + helpId: 'notification/alarm_assignment' + } + ], + [NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, + { + name: 'notification.template-type.rule-engine-lifecycle-event', + helpId: 'notification/rule_engine_lifecycle_event' + } + ], + [NotificationType.ENTITIES_LIMIT, + { + name: 'notification.template-type.entities-limit', + helpId: 'notification/entities_limit' + } + ], + [NotificationType.API_USAGE_LIMIT, + { + name: 'notification.template-type.api-usage-limit', + helpId: 'notification/api_usage_limit' + } + ], + [NotificationType.RULE_ENGINE, + { + name: 'notification.template-type.rule-engine', + helpId: 'notification/rule_engine' + } + ] +]); + +export enum TriggerType { + ALARM = 'ALARM', + DEVICE_ACTIVITY = 'DEVICE_ACTIVITY', + ENTITY_ACTION = 'ENTITY_ACTION', + ALARM_COMMENT = 'ALARM_COMMENT', + ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT', + RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT', + ENTITIES_LIMIT = 'ENTITIES_LIMIT', + API_USAGE_LIMIT = 'API_USAGE_LIMIT' +} + +export const TriggerTypeTranslationMap = new Map([ + [TriggerType.ALARM, 'notification.trigger.alarm'], + [TriggerType.DEVICE_ACTIVITY, 'notification.trigger.device-activity'], + [TriggerType.ENTITY_ACTION, 'notification.trigger.entity-action'], + [TriggerType.ALARM_COMMENT, 'notification.trigger.alarm-comment'], + [TriggerType.ALARM_ASSIGNMENT, 'notification.trigger.alarm-assignment'], + [TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'notification.trigger.rule-engine-lifecycle-event'], + [TriggerType.ENTITIES_LIMIT, 'notification.trigger.entities-limit'], + [TriggerType.API_USAGE_LIMIT, 'notification.trigger.api-usage-limit'], +]); diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts index b774546547..483a9553ab 100644 --- a/ui-ngx/src/app/shared/models/oauth2.models.ts +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/ota-package.models.ts b/ui-ngx/src/app/shared/models/ota-package.models.ts index 34963be66b..3af7b9ae30 100644 --- a/ui-ngx/src/app/shared/models/ota-package.models.ts +++ b/ui-ngx/src/app/shared/models/ota-package.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/page/page-data.ts b/ui-ngx/src/app/shared/models/page/page-data.ts index bc42f93d79..6c144325ee 100644 --- a/ui-ngx/src/app/shared/models/page/page-data.ts +++ b/ui-ngx/src/app/shared/models/page/page-data.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index 683b83298c..e55ac494e0 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/page/public-api.ts b/ui-ngx/src/app/shared/models/page/public-api.ts index eefdd67f71..3bf44ef8e7 100644 --- a/ui-ngx/src/app/shared/models/page/public-api.ts +++ b/ui-ngx/src/app/shared/models/page/public-api.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/page/sort-order.ts b/ui-ngx/src/app/shared/models/page/sort-order.ts index 253d3b313b..f21840c6eb 100644 --- a/ui-ngx/src/app/shared/models/page/sort-order.ts +++ b/ui-ngx/src/app/shared/models/page/sort-order.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index 8c97669481..1629c656da 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ export * from './telemetry/telemetry.models'; export * from './time/time.models'; export * from './alarm.models'; export * from './alias.models'; +export * from './api-usage.models'; export * from './asset.models'; export * from './audit-log.models'; export * from './authority.enum'; @@ -38,6 +39,9 @@ export * from './error.models'; export * from './event.models'; export * from './login.models'; export * from './material.models'; +export * from './notification.models'; +export * from './websocket/notification-ws.models'; +export * from './websocket/websocket.models'; export * from './oauth2.models'; export * from './queue.models'; export * from './relation.models'; @@ -48,6 +52,7 @@ export * from './rule-node.models'; export * from './settings.models'; export * from './tenant.model'; export * from './user.model'; +export * from './user-settings.models'; export * from './widget.models'; export * from './widgets-bundle.model'; export * from './window-message.model'; diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index c1bf255cd3..9c50afecf4 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/queue.models.ts b/ui-ngx/src/app/shared/models/queue.models.ts index 5814a6eeb9..76ee0bf022 100644 --- a/ui-ngx/src/app/shared/models/queue.models.ts +++ b/ui-ngx/src/app/shared/models/queue.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/relation.models.ts b/ui-ngx/src/app/shared/models/relation.models.ts index e42b915e9e..c51c60664c 100644 --- a/ui-ngx/src/app/shared/models/relation.models.ts +++ b/ui-ngx/src/app/shared/models/relation.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/resource.models.ts b/ui-ngx/src/app/shared/models/resource.models.ts index 778cfb57a4..871bf147ea 100644 --- a/ui-ngx/src/app/shared/models/resource.models.ts +++ b/ui-ngx/src/app/shared/models/resource.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/rpc.models.ts b/ui-ngx/src/app/shared/models/rpc.models.ts index b0c97062ae..cbdda5902a 100644 --- a/ui-ngx/src/app/shared/models/rpc.models.ts +++ b/ui-ngx/src/app/shared/models/rpc.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index 01f9d268c3..20086ca21b 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index 0d224c52e5..9d78a5ced8 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import { PageComponent } from '@shared/components/page.component'; import { AfterViewInit, EventEmitter, Inject, OnInit, Directive } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { AbstractControl, FormGroup } from '@angular/forms'; +import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { RuleChainType } from '@shared/models/rule-chain.models'; export interface RuleNodeConfiguration { @@ -77,7 +77,7 @@ export interface IRuleNodeConfigurationComponent { } @Directive() -// tslint:disable-next-line:directive-class-suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix export abstract class RuleNodeConfigurationComponent extends PageComponent implements IRuleNodeConfigurationComponent, OnInit, AfterViewInit { @@ -179,7 +179,7 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple protected onValidate() {} - protected abstract configForm(): FormGroup; + protected abstract configForm(): UntypedFormGroup; protected abstract onConfigurationSet(configuration: RuleNodeConfiguration); @@ -422,6 +422,9 @@ const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.geo.TbGpsGeofencingFilterNode': 'ruleNodeGpsGeofencingFilter', 'org.thingsboard.rule.engine.filter.TbJsFilterNode': 'ruleNodeJsFilter', 'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch', + 'org.thingsboard.rule.engine.filter.TbAssetTypeSwitchNode': 'ruleNodeAssetProfileSwitch', + 'org.thingsboard.rule.engine.filter.TbDeviceTypeSwitchNode': 'ruleNodeDeviceProfileSwitch', + 'org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNode': 'ruleNodeCheckAlarmStatus', 'org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode': 'ruleNodeMessageTypeFilter', 'org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode': 'ruleNodeMessageTypeSwitch', 'org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode': 'ruleNodeOriginatorTypeFilter', diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 256c13cbf5..a673e0cacc 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 1cd44652a9..44af6afc30 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import { AggregationType } from '../time/time.models'; -import { Observable, ReplaySubject, Subject } from 'rxjs'; +import { Observable, ReplaySubject } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { map } from 'rxjs/operators'; import { NgZone } from '@angular/core'; @@ -33,6 +33,8 @@ import { PageData } from '@shared/models/page/page-data'; import { alarmFields } from '@shared/models/alarm.models'; import { entityFields } from '@shared/models/entity.models'; import { isUndefined } from '@core/utils'; +import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; export enum DataKeyType { timeseries = 'timeseries', @@ -60,13 +62,13 @@ export enum TelemetryFeature { export type TelemetryType = LatestTelemetry | AttributeScope; -export function toTelemetryType(val: string): TelemetryType { +export const toTelemetryType = (val: string): TelemetryType => { if (LatestTelemetry[val]) { return LatestTelemetry[val]; } else { return AttributeScope[val]; } -} +}; export const telemetryTypeTranslations = new Map( [ @@ -234,7 +236,7 @@ export class AlarmDataUnsubscribeCmd implements WebsocketCmd { cmdId: number; } -export class TelemetryPluginCmdsWrapper { +export class TelemetryPluginCmdsWrapper implements CmdWrapper { constructor() { this.attrSubCmds = []; @@ -335,7 +337,9 @@ export interface SubscriptionUpdateMsg extends SubscriptionDataHolder { export enum CmdUpdateType { ENTITY_DATA = 'ENTITY_DATA', ALARM_DATA = 'ALARM_DATA', - COUNT_DATA = 'COUNT_DATA' + COUNT_DATA = 'COUNT_DATA', + NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT', + NOTIFICATIONS = 'NOTIFICATIONS' } export interface CmdUpdateMsg { @@ -367,20 +371,20 @@ export interface EntityCountUpdateMsg extends CmdUpdateMsg { export type WebsocketDataMsg = AlarmDataUpdateMsg | EntityDataUpdateMsg | EntityCountUpdateMsg | SubscriptionUpdateMsg; -export function isEntityDataUpdateMsg(message: WebsocketDataMsg): message is EntityDataUpdateMsg { +export const isEntityDataUpdateMsg = (message: WebsocketDataMsg): message is EntityDataUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ENTITY_DATA; -} +}; -export function isAlarmDataUpdateMsg(message: WebsocketDataMsg): message is AlarmDataUpdateMsg { +export const isAlarmDataUpdateMsg = (message: WebsocketDataMsg): message is AlarmDataUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_DATA; -} +}; -export function isEntityCountUpdateMsg(message: WebsocketDataMsg): message is EntityCountUpdateMsg { +export const isEntityCountUpdateMsg = (message: WebsocketDataMsg): message is EntityCountUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.COUNT_DATA; -} +}; export class SubscriptionUpdate implements SubscriptionUpdateMsg { subscriptionId: number; @@ -562,31 +566,20 @@ export class EntityCountUpdate extends CmdUpdate { } } -export interface TelemetryService { - subscribe(subscriber: TelemetrySubscriber); - update(subscriber: TelemetrySubscriber); - unsubscribe(subscriber: TelemetrySubscriber); -} - -export class TelemetrySubscriber { +export class TelemetrySubscriber extends WsSubscriber { private dataSubject = new ReplaySubject(1); private entityDataSubject = new ReplaySubject(1); private alarmDataSubject = new ReplaySubject(1); private entityCountSubject = new ReplaySubject(1); - private reconnectSubject = new Subject(); - private tsOffset = undefined; - public subscriptionCommands: Array; - public data$ = this.dataSubject.asObservable(); public entityData$ = this.entityDataSubject.asObservable(); public alarmData$ = this.alarmDataSubject.asObservable(); public entityCount$ = this.entityCountSubject.asObservable(); - public reconnect$ = this.reconnectSubject.asObservable(); - public static createEntityAttributesSubscription(telemetryService: TelemetryService, + public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService, entityId: EntityId, attributeScope: TelemetryType, zone: NgZone, keys: string[] = null): TelemetrySubscriber { let subscriptionCommand: SubscriptionCmd; @@ -606,21 +599,8 @@ export class TelemetrySubscriber { return subscriber; } - constructor(private telemetryService: TelemetryService, private zone?: NgZone) { - this.subscriptionCommands = []; - } - - public subscribe() { - this.telemetryService.subscribe(this); - } - - public update() { - this.telemetryService.update(this); - } - - public unsubscribe() { - this.telemetryService.unsubscribe(this); - this.complete(); + constructor(private telemetryService: TelemetryWebsocketService, protected zone?: NgZone) { + super(telemetryService, zone); } public complete() { @@ -628,7 +608,7 @@ export class TelemetrySubscriber { this.entityDataSubject.complete(); this.alarmDataSubject.complete(); this.entityCountSubject.complete(); - this.reconnectSubject.complete(); + super.complete(); } public setTsOffset(tsOffset: number): boolean { @@ -705,10 +685,6 @@ export class TelemetrySubscriber { } } - public onReconnected() { - this.reconnectSubject.next(); - } - public attributeData$(): Observable> { const attributeData = new Array(); return this.data$.pipe( diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 27b86583ea..95e0314a52 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ export interface DefaultTenantProfileConfiguration { tenantEntityExportRateLimit?: string; tenantEntityImportRateLimit?: string; + tenantNotificationRequestsRateLimit?: string; maxTransportMessages: number; maxTransportDataPoints: number; diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index d657d5cda4..ae0cfe0b34 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -140,6 +140,8 @@ export enum QuickTimeInterval { PREVIOUS_WEEK = 'PREVIOUS_WEEK', PREVIOUS_WEEK_ISO = 'PREVIOUS_WEEK_ISO', PREVIOUS_MONTH = 'PREVIOUS_MONTH', + PREVIOUS_QUARTER = 'PREVIOUS_QUARTER', + PREVIOUS_HALF_YEAR = 'PREVIOUS_HALF_YEAR', PREVIOUS_YEAR = 'PREVIOUS_YEAR', CURRENT_HOUR = 'CURRENT_HOUR', CURRENT_DAY = 'CURRENT_DAY', @@ -150,6 +152,10 @@ export enum QuickTimeInterval { CURRENT_WEEK_ISO_SO_FAR = 'CURRENT_WEEK_ISO_SO_FAR', CURRENT_MONTH = 'CURRENT_MONTH', CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', + CURRENT_QUARTER = 'CURRENT_QUARTER', + CURRENT_QUARTER_SO_FAR = 'CURRENT_QUARTER_SO_FAR', + CURRENT_HALF_YEAR = 'CURRENT_HALF_YEAR', + CURRENT_HALF_YEAR_SO_FAR = 'CURRENT_HALF_YEAR_SO_FAR', CURRENT_YEAR = 'CURRENT_YEAR', CURRENT_YEAR_SO_FAR = 'CURRENT_YEAR_SO_FAR' } @@ -161,6 +167,8 @@ export const QuickTimeIntervalTranslationMap = new Map { tenantId: TenantId; customerId: CustomerId; email: string; + phone: string; authority: Authority; firstName: string; lastName: string; @@ -54,3 +55,10 @@ export interface AuthUser { isPublic: boolean; authority: Authority; } + +export interface UserEmailInfo { + id: UserId; + email: string; + firstName: string; + lastName: string; +} diff --git a/ui-ngx/src/app/shared/models/vc.models.ts b/ui-ngx/src/app/shared/models/vc.models.ts index d92dd26f17..155a9c8633 100644 --- a/ui-ngx/src/app/shared/models/vc.models.ts +++ b/ui-ngx/src/app/shared/models/vc.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts new file mode 100644 index 0000000000..0acbba1139 --- /dev/null +++ b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts @@ -0,0 +1,240 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { BehaviorSubject, ReplaySubject } from 'rxjs'; +import { CmdUpdate, CmdUpdateMsg, CmdUpdateType, WebsocketCmd } from '@shared/models/telemetry/telemetry.models'; +import { map } from 'rxjs/operators'; +import { NgZone } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/utils'; +import { Notification } from '@shared/models/notification.models'; +import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models'; +import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; + +export class NotificationCountUpdate extends CmdUpdate { + totalUnreadCount: number; + + constructor(msg: NotificationCountUpdateMsg) { + super(msg); + this.totalUnreadCount = msg.totalUnreadCount; + } +} + +export class NotificationsUpdate extends CmdUpdate { + totalUnreadCount: number; + update?: Notification; + notifications?: Notification[]; + + constructor(msg: NotificationsUpdateMsg) { + super(msg); + this.totalUnreadCount = msg.totalUnreadCount; + this.update = msg.update; + this.notifications = msg.notifications; + } +} + +export class NotificationSubscriber extends WsSubscriber { + private notificationCountSubject = new ReplaySubject(1); + private notificationsSubject = new BehaviorSubject({ + cmdId: 0, + cmdUpdateType: undefined, + errorCode: 0, + errorMsg: '', + notifications: [], + totalUnreadCount: 0 + }); + + public messageLimit = 10; + + public notificationCount$ = this.notificationCountSubject.asObservable().pipe(map(msg => msg.totalUnreadCount)); + public notifications$ = this.notificationsSubject.asObservable().pipe(map(msg => msg.notifications )); + + public static createNotificationCountSubscription(notificationWsService: NotificationWebsocketService, + zone: NgZone): NotificationSubscriber { + const subscriptionCommand = new UnreadCountSubCmd(); + const subscriber = new NotificationSubscriber(notificationWsService, zone); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + public static createNotificationsSubscription(notificationWsService: NotificationWebsocketService, + zone: NgZone, limit = 10): NotificationSubscriber { + const subscriptionCommand = new UnreadSubCmd(limit); + const subscriber = new NotificationSubscriber(notificationWsService, zone); + subscriber.messageLimit = limit; + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + public static createMarkAsReadCommand(notificationWsService: NotificationWebsocketService, + ids: string[]): NotificationSubscriber { + const subscriptionCommand = new MarkAsReadCmd(ids); + const subscriber = new NotificationSubscriber(notificationWsService); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + public static createMarkAllAsReadCommand(notificationWsService: NotificationWebsocketService): NotificationSubscriber { + const subscriptionCommand = new MarkAllAsReadCmd(); + const subscriber = new NotificationSubscriber(notificationWsService); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + constructor(private notificationWsService: NotificationWebsocketService, protected zone?: NgZone) { + super(notificationWsService, zone); + } + + onNotificationCountUpdate(message: NotificationCountUpdate) { + if (this.zone) { + this.zone.run( + () => { + this.notificationCountSubject.next(message); + } + ); + } else { + this.notificationCountSubject.next(message); + } + } + + public complete() { + this.notificationCountSubject.complete(); + this.notificationsSubject.complete(); + super.complete(); + } + + onNotificationsUpdate(message: NotificationsUpdate) { + const currentNotifications = this.notificationsSubject.value; + let processMessage = message; + if (isDefinedAndNotNull(currentNotifications) && message.update) { + currentNotifications.notifications.unshift(message.update); + if (currentNotifications.notifications.length > this.messageLimit) { + currentNotifications.notifications.pop(); + } + processMessage = currentNotifications; + processMessage.totalUnreadCount = message.totalUnreadCount; + } + if (this.zone) { + this.zone.run( + () => { + this.notificationsSubject.next(processMessage); + this.notificationCountSubject.next(processMessage); + } + ); + } else { + this.notificationsSubject.next(processMessage); + this.notificationCountSubject.next(processMessage); + } + } +} + +export class UnreadCountSubCmd implements WebsocketCmd { + cmdId: number; +} + +export class UnreadSubCmd implements WebsocketCmd { + limit: number; + cmdId: number; + + constructor(limit = 10) { + this.limit = limit; + } +} + +export class UnsubscribeCmd implements WebsocketCmd { + cmdId: number; +} + +export class MarkAsReadCmd implements WebsocketCmd { + + cmdId: number; + notifications: string[]; + + constructor(ids: string[]) { + this.notifications = ids; + } +} + +export class MarkAllAsReadCmd implements WebsocketCmd { + cmdId: number; +} + +export interface NotificationCountUpdateMsg extends CmdUpdateMsg { + cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT; + totalUnreadCount: number; +} + +export interface NotificationsUpdateMsg extends CmdUpdateMsg { + cmdUpdateType: CmdUpdateType.NOTIFICATIONS; + totalUnreadCount: number; + update?: Notification; + notifications?: Notification[]; +} + +export type WebsocketNotificationMsg = NotificationCountUpdateMsg | NotificationsUpdateMsg; + +export const isNotificationCountUpdateMsg = (message: WebsocketNotificationMsg): message is NotificationCountUpdateMsg => { + const updateMsg = (message as CmdUpdateMsg); + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT; +}; + +export const isNotificationsUpdateMsg = (message: WebsocketNotificationMsg): message is NotificationsUpdateMsg => { + const updateMsg = (message as CmdUpdateMsg); + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS; +}; + +export class NotificationPluginCmdWrapper implements CmdWrapper { + + constructor() { + this.unreadCountSubCmd = null; + this.unreadSubCmd = null; + this.unsubCmd = null; + this.markAsReadCmd = null; + this.markAllAsReadCmd = null; + } + + unreadCountSubCmd: UnreadCountSubCmd; + unreadSubCmd: UnreadSubCmd; + unsubCmd: UnsubscribeCmd; + markAsReadCmd: MarkAsReadCmd; + markAllAsReadCmd: MarkAllAsReadCmd; + + public hasCommands(): boolean { + return isDefinedAndNotNull(this.unreadCountSubCmd) || + isDefinedAndNotNull(this.unreadSubCmd) || + isDefinedAndNotNull(this.unsubCmd) || + isDefinedAndNotNull(this.markAsReadCmd) || + isDefinedAndNotNull(this.markAllAsReadCmd); + } + + public clear() { + this.unreadCountSubCmd = null; + this.unreadSubCmd = null; + this.unsubCmd = null; + this.markAsReadCmd = null; + this.markAllAsReadCmd = null; + } + + public preparePublishCommands(): NotificationPluginCmdWrapper { + const preparedWrapper = new NotificationPluginCmdWrapper(); + preparedWrapper.unreadCountSubCmd = this.unreadCountSubCmd || undefined; + preparedWrapper.unreadSubCmd = this.unreadSubCmd || undefined; + preparedWrapper.unsubCmd = this.unsubCmd || undefined; + preparedWrapper.markAsReadCmd = this.markAsReadCmd || undefined; + preparedWrapper.markAllAsReadCmd = this.markAllAsReadCmd || undefined; + this.clear(); + return preparedWrapper; + } +} diff --git a/ui-ngx/src/app/shared/models/websocket/websocket.models.ts b/ui-ngx/src/app/shared/models/websocket/websocket.models.ts new file mode 100644 index 0000000000..fc7fc3d15c --- /dev/null +++ b/ui-ngx/src/app/shared/models/websocket/websocket.models.ts @@ -0,0 +1,67 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgZone } from '@angular/core'; +import { WebsocketCmd } from '@shared/models/telemetry/telemetry.models'; +import { Subject } from 'rxjs'; + +export interface WsService { + subscribe(subscriber: T); + update(subscriber: T); + unsubscribe(subscriber: T); +} + +export abstract class CmdWrapper { + abstract hasCommands(): boolean; + abstract clear(): void; + abstract preparePublishCommands(maxCommands: number): CmdWrapper; + + [key: string]: WebsocketCmd | any; +} + +export abstract class WsSubscriber { + + protected reconnectSubject = new Subject(); + + subscriptionCommands: Array; + + reconnect$ = this.reconnectSubject.asObservable(); + + protected constructor(protected wsService: WsService, protected zone?: NgZone) { + this.subscriptionCommands = []; + } + + public subscribe() { + this.wsService.subscribe(this); + } + + public update() { + this.wsService.update(this); + } + + public unsubscribe() { + this.wsService.unsubscribe(this); + this.complete(); + } + + public complete() { + this.reconnectSubject.complete(); + } + + public onReconnected() { + this.reconnectSubject.next(); + } +} diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 72d11626cb..bd7e051b58 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import { PageComponent } from '@shared/components/page.component'; import { AfterViewInit, Directive, EventEmitter, Inject, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { AbstractControl, FormGroup } from '@angular/forms'; +import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; @@ -706,7 +706,7 @@ function removeEmptyWidgetSettings(settings: WidgetSettings): WidgetSettings { } @Directive() -// tslint:disable-next-line:directive-class-suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix export abstract class WidgetSettingsComponent extends PageComponent implements IWidgetSettingsComponent, OnInit, AfterViewInit { @@ -802,7 +802,7 @@ export abstract class WidgetSettingsComponent extends PageComponent implements } } - protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) { + protected doUpdateSettings(settingsForm: UntypedFormGroup, settings: WidgetSettings) { } protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { @@ -819,7 +819,7 @@ export abstract class WidgetSettingsComponent extends PageComponent implements protected onValidate() {} - protected abstract settingsForm(): FormGroup; + protected abstract settingsForm(): UntypedFormGroup; protected abstract onSettingsSet(settings: WidgetSettings); diff --git a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts index 235190ead2..c7eb999221 100644 --- a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts +++ b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/window-message.model.ts b/ui-ngx/src/app/shared/models/window-message.model.ts index dff6898958..f720f2692f 100644 --- a/ui-ngx/src/app/shared/models/window-message.model.ts +++ b/ui-ngx/src/app/shared/models/window-message.model.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts new file mode 100644 index 0000000000..c0f4b5c026 --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts @@ -0,0 +1,58 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Inject, Pipe, PipeTransform } from '@angular/core'; +import { DAY, HOUR, MINUTE, SECOND, WEEK, YEAR } from '@shared/models/time/time.models'; +import { TranslateService } from '@ngx-translate/core'; + +const intervals = { + years: YEAR, + months: DAY * 30, + weeks: WEEK, + days: DAY, + hr: HOUR, + min: MINUTE, + sec: SECOND +}; + +@Pipe({ + name: 'dateAgo' +}) +export class DateAgoPipe implements PipeTransform { + + constructor(@Inject(TranslateService) private translate: TranslateService) { + + } + + transform(value: number): string { + if (value) { + const ms = Math.floor((+new Date() - +new Date(value))); + if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now' + return this.translate.instant('timewindow.just-now'); + } + let counter; + // tslint:disable-next-line:forin + for (const i in intervals) { + counter = Math.floor(ms / intervals[i]); + if (counter > 0) { + return this.translate.instant(`timewindow.${i}`, {[i]: counter}); + } + } + } + return ''; + } + +} diff --git a/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts b/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts index e879727dd5..befd9a7b56 100644 --- a/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/file-size.pipe.ts b/ui-ngx/src/app/shared/pipe/file-size.pipe.ts index bfab9ee5c3..dc67cd216d 100644 --- a/ui-ngx/src/app/shared/pipe/file-size.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/file-size.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/highlight.pipe.ts b/ui-ngx/src/app/shared/pipe/highlight.pipe.ts index 5a676cc96b..0f8595fc3b 100644 --- a/ui-ngx/src/app/shared/pipe/highlight.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/highlight.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts b/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts index b11c518d40..12104356d0 100644 --- a/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts b/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts index 6722f2f72e..df185fe8d0 100644 --- a/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -25,33 +25,51 @@ export class MillisecondsToTimeStringPipe implements PipeTransform { constructor(private translate: TranslateService) { } - transform(millseconds: number, args?: any): string { + transform(millseconds: number, shortFormat = false): string { let seconds = Math.floor(millseconds / 1000); const days = Math.floor(seconds / 86400); let hours = Math.floor((seconds % 86400) / 3600); let minutes = Math.floor(((seconds % 86400) % 3600) / 60); seconds = seconds % 60; let timeString = ''; - if (days > 0) { - timeString += this.translate.instant('timewindow.days', {days}); - } - if (hours > 0) { - if (timeString.length === 0 && hours === 1) { - hours = 0; + if (shortFormat) { + if (days > 0) { + timeString += this.translate.instant('timewindow.short.days', {days}); } - timeString += this.translate.instant('timewindow.hours', {hours}); - } - if (minutes > 0) { - if (timeString.length === 0 && minutes === 1) { - minutes = 0; + if (hours > 0) { + timeString += this.translate.instant('timewindow.short.hours', {hours}); } - timeString += this.translate.instant('timewindow.minutes', {minutes}); - } - if (seconds > 0) { - if (timeString.length === 0 && seconds === 1) { - seconds = 0; + if (minutes > 0) { + timeString += this.translate.instant('timewindow.short.minutes', {minutes}); + } + if (seconds > 0) { + timeString += this.translate.instant('timewindow.short.seconds', {seconds}); + } + if (!timeString.length) { + timeString += this.translate.instant('timewindow.short.seconds', {seconds: 0}); + } + } else { + if (days > 0) { + timeString += this.translate.instant('timewindow.days', {days}); + } + if (hours > 0) { + if (timeString.length === 0 && hours === 1) { + hours = 0; + } + timeString += this.translate.instant('timewindow.hours', {hours}); + } + if (minutes > 0) { + if (timeString.length === 0 && minutes === 1) { + minutes = 0; + } + timeString += this.translate.instant('timewindow.minutes', {minutes}); + } + if (seconds > 0) { + if (timeString.length === 0 && seconds === 1) { + seconds = 0; + } + timeString += this.translate.instant('timewindow.seconds', {seconds}); } - timeString += this.translate.instant('timewindow.seconds', {seconds}); } return timeString; } diff --git a/ui-ngx/src/app/shared/pipe/nospace.pipe.ts b/ui-ngx/src/app/shared/pipe/nospace.pipe.ts index d702464d3b..155f648ae0 100644 --- a/ui-ngx/src/app/shared/pipe/nospace.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/nospace.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/public-api.ts b/ui-ngx/src/app/shared/pipe/public-api.ts index dc810aaaf1..9ba3be3610 100644 --- a/ui-ngx/src/app/shared/pipe/public-api.ts +++ b/ui-ngx/src/app/shared/pipe/public-api.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ /// limitations under the License. /// +export * from './date-ago.pipe'; export * from './enum-to-array.pipe'; export * from './highlight.pipe'; export * from './keyboard-shortcut.pipe'; diff --git a/ui-ngx/src/app/shared/pipe/safe.pipe.ts b/ui-ngx/src/app/shared/pipe/safe.pipe.ts index 67243802f5..22cf9e4a50 100644 --- a/ui-ngx/src/app/shared/pipe/safe.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/safe.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts b/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts index 9ee424bd68..e71ba26787 100644 --- a/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/tbJson.pipe.ts b/ui-ngx/src/app/shared/pipe/tbJson.pipe.ts index 41385c2f1a..edf55e1f0c 100644 --- a/ui-ngx/src/app/shared/pipe/tbJson.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/tbJson.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/truncate.pipe.ts b/ui-ngx/src/app/shared/pipe/truncate.pipe.ts index 5e7c8d6f69..db2d0ccb09 100644 --- a/ui-ngx/src/app/shared/pipe/truncate.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/truncate.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/public-api.ts b/ui-ngx/src/app/shared/public-api.ts index e289cc9339..f63b26e18c 100644 --- a/ui-ngx/src/app/shared/public-api.ts +++ b/ui-ngx/src/app/shared/public-api.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/services/custom-paginator-intl.ts b/ui-ngx/src/app/shared/services/custom-paginator-intl.ts index ef2ab076d5..fa52a99f3b 100644 --- a/ui-ngx/src/app/shared/services/custom-paginator-intl.ts +++ b/ui-ngx/src/app/shared/services/custom-paginator-intl.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 720f74d2a6..c82ae61644 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -24,7 +24,8 @@ import { FlowInjectionToken, NgxFlowModule } from '@flowjs/ngx-flow'; import { NgxFlowchartModule } from 'ngx-flowchart'; import Flow from '@flowjs/flow.js'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; @@ -42,7 +43,7 @@ import { MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginato import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatRadioModule } from '@angular/material/radio'; -import { MatSelectModule } from '@angular/material/select'; +import { MAT_SELECT_CONFIG, MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSliderModule } from '@angular/material/slider'; @@ -64,6 +65,7 @@ import { ShareModule as ShareButtonsModule } from 'ngx-sharebuttons'; import { HotkeyModule } from 'angular2-hotkeys'; import { ColorPickerModule } from 'ngx-color-picker'; import { NgxHmCarouselModule } from 'ngx-hm-carousel'; +import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { NospacePipe } from '@shared/pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; @@ -84,6 +86,7 @@ import { MarkdownEditorComponent } from '@shared/components/markdown-editor.comp import { FullscreenDirective } from '@shared/components/fullscreen.directive'; import { HighlightPipe } from '@shared/pipe/highlight.pipe'; import { DashboardAutocompleteComponent } from '@shared/components/dashboard-autocomplete.component'; +import { DashboardStateAutocompleteComponent } from '@shared/components/dashboard-state-autocomplete.component'; import { EntitySubTypeAutocompleteComponent } from '@shared/components/entity/entity-subtype-autocomplete.component'; import { EntitySubTypeSelectComponent } from '@shared/components/entity/entity-subtype-select.component'; import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component'; @@ -93,6 +96,7 @@ import { EntitySelectComponent } from '@shared/components/entity/entity-select.c import { DatetimeComponent } from '@shared/components/time/datetime.component'; import { EntityKeysListComponent } from '@shared/components/entity/entity-keys-list.component'; import { SocialSharePanelComponent } from '@shared/components/socialshare-panel.component'; +import { StringItemsListComponent } from '@shared/components/string-items-list.component'; import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; import { EntityListSelectComponent } from '@shared/components/entity/entity-list-select.component'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; @@ -114,7 +118,6 @@ import { EntitySubTypeListComponent } from '@shared/components/entity/entity-sub import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { TbJsonPipe } from '@shared/pipe/tbJson.pipe'; import { ColorPickerDialogComponent } from '@shared/components/dialog/color-picker-dialog.component'; -import { MatChipDraggableDirective } from '@shared/components/mat-chip-draggable.directive'; import { ColorInputComponent } from '@shared/components/color-input.component'; import { JsFuncComponent } from '@shared/components/js-func.component'; import { JsonFormComponent } from '@shared/components/json-form/json-form.component'; @@ -169,6 +172,10 @@ import { PhoneInputComponent } from '@shared/components/phone-input.component'; import { CustomDateAdapter } from '@shared/adapter/custom-datatime-adapter'; import { CustomPaginatorIntl } from '@shared/services/custom-paginator-intl'; import { TbScriptLangComponent } from '@shared/components/script-lang.component'; +import { NotificationComponent } from '@shared/components/notification/notification.component'; +import { TemplateAutocompleteComponent } from '@shared/components/notification/template-autocomplete.component'; +import { SlackConversationAutocompleteComponent } from '@shared/components/slack-conversation-autocomplete.component'; +import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -183,11 +190,17 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TruncatePipe, TbJsonPipe, FileSizePipe, + DateAgoPipe, SafePipe, + DateAgoPipe, { provide: FlowInjectionToken, useValue: Flow }, + { + provide: TINYMCE_SCRIPT_SRC, + useValue: 'assets/tinymce/tinymce.min.js' + }, { provide: MAT_DATE_LOCALE, useValue: 'en-GB' @@ -196,7 +209,20 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { provide: HELP_MARKDOWN_COMPONENT_TOKEN, useValue: HelpMarkdownComponent }, { provide: SHARED_MODULE_TOKEN, useValue: SharedModule }, { provide: MatPaginatorIntl, useClass: CustomPaginatorIntl }, - TbPopoverService + TbPopoverService, + { + provide: MAT_SELECT_CONFIG, + useValue: { + overlayPanelClass: 'tb-select-overlay', + hideSingleSelectionIndicator: true + } + }, + { + provide: MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, + useValue: { + hideSingleSelectionIndicator: true + } + } ], declarations: [ FooterComponent, @@ -205,7 +231,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ToastDirective, FullscreenDirective, CircularProgressDirective, - MatChipDraggableDirective, TbHotkeysDirective, TbAnchorComponent, TbPopoverComponent, @@ -233,6 +258,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezoneSelectComponent, ValueInputComponent, DashboardAutocompleteComponent, + DashboardStateAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntitySubTypeListComponent, @@ -246,6 +272,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) QueueAutocompleteComponent, RelationTypeAutocompleteComponent, SocialSharePanelComponent, + StringItemsListComponent, JsonObjectEditComponent, JsonObjectViewComponent, JsonContentComponent, @@ -280,6 +307,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TruncatePipe, TbJsonPipe, FileSizePipe, + DateAgoPipe, SafePipe, SelectableColumnsPipe, KeyboardShortcutPipe, @@ -295,12 +323,17 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ProtobufContentComponent, BranchAutocompleteComponent, PhoneInputComponent, - TbScriptLangComponent + TbScriptLangComponent, + NotificationComponent, + TemplateAutocompleteComponent, + SlackConversationAutocompleteComponent, + DateAgoPipe ], imports: [ CommonModule, RouterModule, TranslateModule, + MatBadgeModule, MatButtonModule, MatButtonToggleModule, MatCheckboxModule, @@ -365,7 +398,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ToastDirective, FullscreenDirective, CircularProgressDirective, - MatChipDraggableDirective, TbHotkeysDirective, TbAnchorComponent, TbStringTemplateOutletDirective, @@ -389,6 +421,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) DatetimeComponent, TimezoneSelectComponent, DashboardAutocompleteComponent, + DashboardStateAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntitySubTypeListComponent, @@ -402,6 +435,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) QueueAutocompleteComponent, RelationTypeAutocompleteComponent, SocialSharePanelComponent, + StringItemsListComponent, JsonObjectEditComponent, JsonObjectViewComponent, JsonContentComponent, @@ -413,6 +447,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) FabToolbarComponent, WidgetsBundleSelectComponent, ValueInputComponent, + MatBadgeModule, MatButtonModule, MatButtonToggleModule, MatCheckboxModule, @@ -457,6 +492,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) HotkeyModule, ColorPickerModule, NgxHmCarouselModule, + EditorModule, DndModule, NgxFlowchartModule, MarkdownModule, @@ -485,6 +521,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TbJsonPipe, KeyboardShortcutPipe, FileSizePipe, + DateAgoPipe, SafePipe, SelectableColumnsPipe, RouterModule, @@ -500,7 +537,11 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ProtobufContentComponent, BranchAutocompleteComponent, PhoneInputComponent, - TbScriptLangComponent + TbScriptLangComponent, + NotificationComponent, + TemplateAutocompleteComponent, + SlackConversationAutocompleteComponent, + DateAgoPipe ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/fonts/material-icons.css b/ui-ngx/src/assets/fonts/material-icons.css index f790ddaf43..3809ea3696 100644 --- a/ui-ngx/src/assets/fonts/material-icons.css +++ b/ui-ngx/src/assets/fonts/material-icons.css @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm.md b/ui-ngx/src/assets/help/en_US/notification/alarm.md new file mode 100644 index 0000000000..671f617bab --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/alarm.md @@ -0,0 +1,63 @@ +#### Alarm notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *alarmType* - alarm type; + * *action* - one of: 'created', 'severity changed', 'acknowledged', 'cleared', 'deleted'; + * *alarmId* - the alarm id as uuid string; + * *alarmSeverity* - alarm severity (lower case); + * *alarmStatus* - the alarm status; + * *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; + * *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; + * *alarmOriginatorId* - the alarm originator entity id as uuid string; + +Parameter names must be wrapped using `${...}`. For example: `${action}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + + * Let's assume the notification about new alarm with type 'High Temperature' for device 'Sensor A'. The following template: + +```text +Alarm '${alarmType}' - ${action:upperCase} +{:copy-code} +``` + +will be transformed to: + +```text +Alarm 'High Temperature' - CREATED +{:copy-code} +``` + +The following template: + +```text +${alarmOriginatorEntityType:capitalize} '${alarmOriginatorName}' +{:copy-code} +``` + +will be transformed to: + +```text +Device - Sensor A +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md b/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md new file mode 100644 index 0000000000..c4ff27d333 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md @@ -0,0 +1,70 @@ +#### Alarm assignment notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *alarmType* - alarm type; + * *alarmId* - the alarm id as uuid string; + * *alarmSeverity* - alarm severity (lower case); + * *alarmStatus* - the alarm status; + * *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; + * *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; + * *alarmOriginatorId* - the alarm originator entity id as uuid string; + * *assigneeEmail* - email of the assignee; + * *assigneeFirstName* - first name of the assignee; + * *assigneeLastName* - last name of the assignee; + * *assigneeId* - the id of the assignee as uuid string; + * *userEmail* - email of the user who made the action; + * *userFirstName* - first name of the user who made the action; + * *userLastName* - last name of the user who made the action; + * *action* - one of: 'assigned', 'unassigned'; + +Parameter names must be wrapped using `${...}`. For example: `${action}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + + * Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned to user 'John Doe'. The following template: + +```text +Alarm '${alarmType}' - ${action:upperCase} +{:copy-code} +``` + +will be transformed to: + +```text +Alarm 'High Temperature' - ASSIGNED +{:copy-code} +``` + +The following template: + +```text +Alarm '${alarmType}' (${alarmSeverity:capitalize}) was assigned to user +{:copy-code} +``` + +will be transformed to: + +```text +Alarm 'High Temperature' (Critical) was assigned to user +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md b/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md new file mode 100644 index 0000000000..8a008ecf27 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md @@ -0,0 +1,68 @@ +#### Alarm comment notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *alarmType* - alarm type; + * *alarmId* - the alarm id as uuid string; + * *alarmSeverity* - alarm severity (lower case); + * *alarmStatus* - the alarm status; + * *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; + * *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; + * *alarmOriginatorId* - the alarm originator entity id as uuid string; + * *comment* - text of the comment; + * *action* - one of: 'added', 'updated'; + * *userEmail* - email of the user who made the action; + * *userFirstName* - first name of the user who made the action; + * *userLastName* - last name of the user who made the action; + +Parameter names must be wrapped using `${...}`. For example: `${action}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + + * Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned to user 'John Doe'. + The following template: + +```text +Alarm '${alarmType}' - comment ${action} +{:copy-code} +``` + +will be transformed to: + +```text +Alarm 'High Temperature' - comment added +{:copy-code} +``` + +The following template: + +```text +Alarm '${alarmType}' (${alarmSeverity:capitalize}) was commented +{:copy-code} +``` + +will be transformed to: + +```text +Alarm 'High Temperature' (Critical) was commented +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md b/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md new file mode 100644 index 0000000000..29bd757067 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md @@ -0,0 +1,48 @@ +#### API usage limit notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *feature* - API feature for which the limit is applied; one of: 'Device API', 'Telemetry persistence', 'Rule Engine execution', 'JavaScript functions execution', 'Email messages', 'SMS messages', 'Alarms'; + * *status* - one of: 'enabled', 'warning', 'disabled'; + * *unitLabel* - name of the limited unit; one of: 'message', 'data point', 'Rule Engine execution', 'JavaScript execution', 'email message', 'SMS message', 'alarm'; + * *limit* - the limit on used feature units; + * *currentValue* - current number of used units; + * *tenantId* - id of the tenant; + * *tenantName* - name of the tenant; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume tenant's devices pushed 8K messages with the max allowed number of 10K and warn threshold in tenant profile set to 0.8 (80%). The following template: + +```text +${feature} feature - ${status:upperCase} (usage: ${currentValue} out of ${limit} ${unitLabel}s) +{:copy-code} +``` + +will be transformed to: + +```text +Device API feature - WARNING (usage: 8000 out of 10000 messages) +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/device_activity.md b/ui-ngx/src/assets/help/en_US/notification/device_activity.md new file mode 100644 index 0000000000..5e69a0c634 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/device_activity.md @@ -0,0 +1,64 @@ +#### Device activity notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *deviceId* - the device id as uuid string; + * *deviceName* - the device name; + * *deviceLabel* - the device label; + * *deviceType* - the device type; + * *eventType* - one of: 'inactive', 'active'; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume the notification about inactive thermometer device 'Sensor T1'. +The following template: + +Template message: `` + +```text +Device '${deviceName}' inactive +{:copy-code} +``` + +will be transformed to: + +```text +Device 'Sensor T1' inactive +{:copy-code} +``` + + +
    +The following template: + +```text +${deviceType:capitalize} '${deviceName}' became ${eventType} +{:copy-code} +``` + +will be transformed to: + +```text +Thermometer 'Sensor T1' became inactive +{:copy-code} +``` +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/entities_limit.md b/ui-ngx/src/assets/help/en_US/notification/entities_limit.md new file mode 100644 index 0000000000..da351ad30f --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/entities_limit.md @@ -0,0 +1,47 @@ +#### Entity count limit notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *entityType* - one of: 'Device', 'Asset', 'User', etc.; + * *currentCount* - the current count of entities; + * *limit* - the limit on number of entities; + * *percents* - the percent from the notification rule configuration; + * *tenantId* - id of the tenant; + * *tenantName* - name of the tenant; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume the tenant created 400 devices with the max allowed number is 500 and rule threshold 0.8 (80%). The following template: + +```text +${entityType:capitalize}s usage: ${currentCount}/${limit} (${percents}%) +{:copy-code} +``` + +will be transformed to: + +```text +Devices usage: 400/500 (80%) +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/entity_action.md b/ui-ngx/src/assets/help/en_US/notification/entity_action.md new file mode 100644 index 0000000000..06e3fe2d4e --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/entity_action.md @@ -0,0 +1,65 @@ +#### Entity action notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *entityType* - the entity type, e.g. 'Device'; + * *entityId* - the entity id as uuid string; + * *entityName* - the name of the entity; + * *actionType* - one of: 'added', 'updated', 'deleted'; + * *userId* - id of the user who made the action; + * *userEmail* - email of the user who made the action; + * *userFirstName* - first name of the user who made the action; + * *userLastName* - last name of the user who made the action; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume the notification about device 'T1' was added by user 'john.doe@gmail.com'. +The following template: + +```text +${entityType:capitalize} was ${actionType}! +{:copy-code} +``` + +will be transformed to: + +```text +Device was added! +{:copy-code} +``` + +
    +The following template: + +```text +${entityType} '${entityName}' was ${actionType} by user '${originatorUserName}'! +{:copy-code} +``` + +will be transformed to: + +```text +Device 'T1' was added by user 'john.doe@gmail.com'! +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/general.md b/ui-ngx/src/assets/help/en_US/notification/general.md new file mode 100644 index 0000000000..44c8e6aafb --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/general.md @@ -0,0 +1,41 @@ +#### General notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume the notification recipient user `John Doe`. The following template: + +```text +Hi, ${recipientFirstName}! +{:copy-code} +``` + +will be transformed to: + +```text +Hi, John! +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/rule_engine.md b/ui-ngx/src/assets/help/en_US/notification/rule_engine.md new file mode 100644 index 0000000000..30dfa8797d --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/rule_engine.md @@ -0,0 +1,54 @@ +#### Rule engine notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * values from the incoming message metadata; + * values from the incoming message data; + * *originatorType* - type of the originator, e.g. 'Device'; + * *originatorId* - id of the originator + * *msgType* - type of the message + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume the incoming message to Rule node has the following data: +```json +{ + "building_1": { + "temperature": 24 + } +} +``` + +The following template: + +```text +Building 1: temperature is ${building_1.temperature} +{:copy-code} +``` + +will be transformed to: + +```text +Building 1: temperature is 24 +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md b/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md new file mode 100644 index 0000000000..1d22847bed --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md @@ -0,0 +1,49 @@ +#### Rule engine lifecycle notification templatization + +
    +
    + +Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + + * *recipientEmail* - email of the recipient; + * *recipientFirstName* - first name of the recipient; + * *recipientLastName* - last name of the recipient; + * *componentType* - one of: 'Rule chain', 'Rule node'; + * *componentId* - the component id as uuid string; + * *componentName* - the rule chain or rule node name; + * *ruleChainId* - the rule chain id as uuid string; + * *ruleChainName* - the rule chain name; + * *eventType* - one of: 'started', 'updated', 'stopped'; + * *action* - one of: 'start', 'update', 'stop'; + * *error* - the error text; + +Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +You may also modify the value of the parameter with one of the suffixes: + + * `upperCase`, for example - `${recipientFirstName:upperCase}` + * `lowerCase`, for example - `${recipientFirstName:lowerCase}` + * `capitalize`, for example - `${recipientFirstName:capitalize}` + +
    + +##### Examples + +Let's assume the notification about misconfigured Kafka rule node. The following template: + +```text +Rule node '${componentName}' - ${action} failure:
    ${error} +{:copy-code} +``` + +will be transformed to: + +```text +Rule node 'Export to Kafka' - start failure:
    Connection refused! +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md b/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md index d13af3fc26..35ede5703a 100644 --- a/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md +++ b/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md @@ -1,4 +1,4 @@ -#### Additional params object +#### Additional params object

    @@ -16,10 +16,10 @@ An optional key/value object holding additional entity parameters depending on w
-
  • Alarms table widget (On row click or Action cell button) - additionalParams: { alarm: AlarmDataInfo }: +
  • Alarms table widget (On row click or Action cell button) - additionalParams: { alarm: AlarmDataInfo }:
      -
    • alarm: AlarmDataInfo - An - AlarmDataInfo object +
    • alarm: AlarmDataInfo - An + AlarmDataInfo object presenting basic alarm properties (ex. type, severity, originator, etc.) and
      provides access to other alarm or originator entity fields/attributes/timeseries declared in widget datasource configuration.
    diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md index 22738c53c1..130426c6cf 100644 --- a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md +++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md @@ -94,7 +94,7 @@ let activationLinkDialogTemplate = `

    user.activation-link

    -
  • - - - - {{type}} - - - - - - - - - - - - - - - - - +
    +
    + + + + + +
    +
    + +
    +
    - - diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts index f2140e02a5..ab8fb847dd 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -23,10 +23,12 @@ import { OnChanges, OnDestroy, OnInit, - Output, SimpleChanges + Output, + SimpleChanges } from '@angular/core'; import { Color, ColorPickerControl } from '@iplab/ngx-color-picker'; import { Subscription } from 'rxjs'; + export enum ColorType { hex = 'hex', hexa = 'hexa', @@ -46,7 +48,7 @@ export enum ColorType { export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { public selectedPresentation = 0; - public presentations = [ColorType.hex, ColorType.hexa, ColorType.rgb, ColorType.rgba, ColorType.hsl, ColorType.hsla]; + public presentations = [ColorType.hex, ColorType.rgb, ColorType.rgba, ColorType.hsla, ColorType.hsl]; @Input() public color: string; @@ -67,6 +69,12 @@ export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { this.control = new ColorPickerControl(); } + if (this.control.initType === ColorType.hexa) { + this.control.initType = ColorType.hex; + } + + this.selectedPresentation = this.presentations.indexOf(this.control.initType); + if (this.color) { this.control.setValueFrom(this.color); } @@ -74,13 +82,13 @@ export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { this.subscriptions.push( this.control.valueChanges.subscribe((value) => { this.cdr.markForCheck(); - this.colorChange.emit(this.getValueByType(value, this.control.initType)); + this.colorChange.emit(this.getValueByType(value, this.presentations[this.selectedPresentation])); }) ); } changeColorFormat(event: Event) { - this.colorChange.emit(this.getValueByType(this.control.value, this.control.initType)); + this.colorChange.emit(this.getValueByType(this.control.value, this.presentations[this.selectedPresentation])); } public ngOnDestroy(): void { @@ -90,7 +98,8 @@ export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { } public ngOnChanges(changes: SimpleChanges): void { - if (this.color && this.control && this.getValueByType(this.control.value, this.control.initType) !== this.color) { + if (this.color && this.control && + this.getValueByType(this.control.value, this.presentations[this.selectedPresentation]) !== this.color) { this.control.setValueFrom(this.color); } } @@ -98,14 +107,14 @@ export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { public changePresentation(): void { this.selectedPresentation = this.selectedPresentation === this.presentations.length - 1 ? 0 : this.selectedPresentation + 1; + this.colorChange.emit(this.getValueByType(this.control.value, this.presentations[this.selectedPresentation])); + this.cdr.markForCheck(); } getValueByType(color: Color, type: ColorType): string { switch (type) { - case ColorType.hex: - return color.toHexString(); - case ColorType.hexa: - return color.toHexString(true); + case ColorType.hex || ColorType.hexa: + return color.toHexString(this.control.value.getRgba().getAlpha() !== 1); case ColorType.rgb: return color.toRgbString(); case ColorType.rgba: diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index 8de970de65..aedeff35b2 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -15,14 +15,8 @@ limitations under the License. --> - +
    - - - - - -
    diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 784da8da25..8a9d5cbdf8 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -30,8 +30,6 @@ import { import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; import { ColorPickerControl } from '@iplab/ngx-color-picker'; -import { ColorType } from '@shared/components/color-picker/color-picker.component'; -import * as tinycolor_ from 'tinycolor2'; export interface ColorPickerDialogData { color: string; @@ -50,8 +48,6 @@ export class ColorPickerDialogComponent extends DialogComponent, diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 10dbbef4aa..e05275a7af 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -170,7 +170,6 @@ import { CustomPaginatorIntl } from '@shared/services/custom-paginator-intl'; import { TbScriptLangComponent } from '@shared/components/script-lang.component'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component'; -import { ColorPickerInputComponent } from '@shared/components/color-picker/color-picker-input.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -312,8 +311,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) PhoneInputComponent, TbScriptLangComponent, DateAgoPipe, - ColorPickerComponent, - ColorPickerInputComponent + ColorPickerComponent ], imports: [ CommonModule, From 5d84945ccd2d0375bf5fdd66718cd667aab30310 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 12 Apr 2023 14:06:38 +0300 Subject: [PATCH 061/168] Review and improvements --- .../device/DeviceProvisionServiceImpl.java | 84 ++++++++++--------- .../transport/DefaultTransportApiService.java | 38 ++++----- .../dao/device/DeviceProvisionService.java | 3 + .../provision/ProvisionFailedException.java | 4 + .../validator/DeviceProfileDataValidator.java | 14 ++-- 5 files changed, 75 insertions(+), 68 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index e75b3c4b4e..15198d210f 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -99,9 +99,44 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { this.partitionService = partitionService; } + @Override + public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) { + if (targetProfile == null) { + throw new ProvisionFailedException("Device profile is not specified!"); + } + if (!DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(targetProfile.getProfileData().getProvisionConfiguration().getType())) { + throw new ProvisionFailedException("Device profile provision strategy is not X509_CERTIFICATE_CHAIN!"); + } + X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); + String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + String certificateRegEx = configuration.getCertificateRegExPattern(); + String deviceName = extractDeviceNameFromCertificateCNByRegEx(targetProfile, certificateValue, certificateRegEx); + if (StringUtils.isBlank(deviceName)) { + log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]", certificateRegEx, certificateValue); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + provisionRequest.setDeviceName(deviceName); + Device targetDevice = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); + X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, + updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); + } + return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); + } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { + return createDevice(provisionRequest, targetProfile); + } else { + log.warn("[{}][{}] Device with name {} doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration", + targetProfile.getTenantId(), targetProfile.getId(), provisionRequest.getDeviceName()); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } + @Override public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { - fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(provisionRequest); String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) { @@ -148,24 +183,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } break; case X509_CERTIFICATE_CHAIN: - if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { - X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); - if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); - if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); - deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, - updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); - } - return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); - } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { - return createDevice(provisionRequest, targetProfile); - } else { - log.warn("Device with name {} doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration", provisionRequest.getDeviceName()); - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); - } - } - break; + throw new ProvisionFailedException("Invalid provision strategy type!"); } throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } @@ -277,31 +295,21 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); } - private void fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(ProvisionRequest provisionRequest) { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequest.getCredentials().getProvisionDeviceKey()); - if (deviceProfile != null && DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProfileData().getProvisionConfiguration().getType())) { - X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); - String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); - String certificateRegEx = configuration.getCertificateRegExPattern(); - String deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, certificateRegEx); - if (deviceName == null) { - log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]",certificateRegEx, certificateValue); - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); - } - provisionRequest.setDeviceName(deviceName); - } - } - - private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + private String extractDeviceNameFromCertificateCNByRegEx(DeviceProfile profile, String x509Value, String regex) { try { String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commonName); if (matcher.find()) { - return matcher.group(0); + return matcher.group(1); + } else { + return null; } - } catch (Exception ignored) {} - return null; + } catch (Exception ignored) { + log.trace("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", profile.getTenantId(), profile.getId(), regex, x509Value); + return null; + } } + } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 27f20bb5fb..b99c9c2509 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -242,17 +242,20 @@ public class DefaultTransportApiService implements TransportApiService { for (String certificateValue : chain) { String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); - if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + if (credentials != null && DeviceCredentialsType.X509_CERTIFICATE.equals(credentials.getCredentialsType())) { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); - if (deviceProfile != null && deviceProfile.getProvisionType() == X509_CERTIFICATE_CHAIN) { + if (deviceProfile != null && X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) { String updatedDeviceProvisionSecret = chain.get(0); - ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); - ProvisionResponse provisionResponse = provisionDeviceRequestAndGetResponse(provisionRequest); - if (provisionResponse != null && ProvisionResponseStatus.SUCCESS.equals(provisionResponse.getResponseStatus())) { - return getDeviceInfo(provisionResponse.getDeviceCredentials()); - } else { + ProvisionRequest provisionRequest = createProvisionRequest(updatedDeviceProvisionSecret); + try { + ProvisionResponse provisionResponse = deviceProvisionService.provisionDeviceViaX509Chain(deviceProfile, provisionRequest); + if (ProvisionResponseStatus.SUCCESS.equals(provisionResponse.getResponseStatus())) { + return getDeviceInfo(provisionResponse.getDeviceCredentials()); + } + } catch (ProvisionFailedException e) { + log.debug("[{}][{}] Failed to provision device with cert chain: {}", deviceProfile.getTenantId(), deviceProfile.getId(), provisionRequest, e); return getEmptyTransportApiResponseFuture(); } } else if (deviceProfile != null) { @@ -702,23 +705,10 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } - private ProvisionRequest createProvisionRequest(DeviceProfile deviceProfile, String certificateValue) { - ProvisionDeviceProfileCredentials provisionDeviceProfileCredentials = new ProvisionDeviceProfileCredentials( - deviceProfile.getProvisionDeviceKey(), - deviceProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() - ); - ProvisionDeviceCredentialsData provisionDeviceCredentialsData = new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue); - - return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); - } - - private ProvisionResponse provisionDeviceRequestAndGetResponse(ProvisionRequest provisionRequest) { - try { - return deviceProvisionService.provisionDevice(provisionRequest); - } catch (ProvisionFailedException e) { - log.error(e.getMessage()); - } - return null; + private ProvisionRequest createProvisionRequest(String certificateValue) { + return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, + new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), + null); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 77a8d3edbc..fa47ed0694 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; @@ -22,4 +23,6 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; public interface DeviceProvisionService { ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; + + ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java index 2579b1f9cf..f3fc6f97fc 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java @@ -16,7 +16,11 @@ package org.thingsboard.server.dao.device.provision; public class ProvisionFailedException extends RuntimeException { + + private static final long serialVersionUID = 1673991117668477401L; + public ProvisionFailedException(String errorMsg) { super(errorMsg); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 7b142d08e3..d092c34861 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service.validator; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.SecurityUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -72,6 +73,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +@Slf4j @Component public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator { @@ -98,10 +100,10 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Wed, 12 Apr 2023 15:14:38 +0300 Subject: [PATCH 062/168] Notification preview for first recipient; Slack notification preview; increase timeouts for testDelayedNotificationRequest --- .../controller/NotificationController.java | 53 ++++++----- .../NotificationTargetController.java | 2 +- .../DefaultNotificationCenter.java | 2 +- .../notification/NotificationApiTest.java | 94 ++++++++++--------- .../targets/NotificationTargetType.java | 7 ++ 5 files changed, 84 insertions(+), 74 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 6c27887e1a..d36eb9913d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -64,7 +64,9 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import javax.validation.Valid; +import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -201,9 +203,6 @@ public class NotificationController extends BaseController { public NotificationRequestPreview getNotificationRequestPreview(@RequestBody @Valid NotificationRequest request, @RequestParam(defaultValue = "20") int recipientsPreviewSize, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { - NotificationRequestPreview preview = new NotificationRequestPreview(); - - request.setOriginatorEntityId(user.getId()); NotificationTemplate template; if (request.getTemplateId() != null) { template = checkEntityId(request.getTemplateId(), notificationTemplateService::findNotificationTemplateById, Operation.READ); @@ -213,33 +212,23 @@ public class NotificationController extends BaseController { if (template == null) { throw new IllegalArgumentException("Template is missing"); } - NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder() - .tenantId(user.getTenantId()) - .request(request) - .template(template) - .settings(null) - .build(); + request.setOriginatorEntityId(user.getId()); + List targets = request.getTargets().stream() + .map(NotificationTargetId::new) + .map(targetId -> notificationTargetService.findNotificationTargetById(user.getTenantId(), targetId)) + .sorted(Comparator.comparing(target -> target.getConfiguration().getType())) + .collect(Collectors.toList()); - Map processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream() - .collect(Collectors.toMap(m -> m, deliveryMethod -> { - NotificationRecipient recipient = null; - if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) { - recipient = userService.findUserById(user.getTenantId(), user.getId()); - } - return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, recipient); - })); - preview.setProcessedTemplates(processedTemplates); + NotificationRequestPreview preview = new NotificationRequestPreview(); - // generic permission Set recipientsPreview = new LinkedHashSet<>(); - Map recipientsCountByTarget = new HashMap<>(); - - List targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(), - request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList())); + Map recipientsCountByTarget = new LinkedHashMap<>(); + Map firstRecipient = new HashMap<>(); for (NotificationTarget target : targets) { int recipientsCount; List recipientsPart; - if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) { + NotificationTargetType targetType = target.getConfiguration().getType(); + if (targetType == NotificationTargetType.PLATFORM_USERS) { PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize)); recipientsCount = (int) recipients.getTotalElements(); @@ -248,7 +237,7 @@ public class NotificationController extends BaseController { recipientsCount = 1; recipientsPart = List.of(((SlackNotificationTargetConfig) target.getConfiguration()).getConversation()); } - + firstRecipient.putIfAbsent(targetType, !recipientsPart.isEmpty() ? recipientsPart.get(0) : null); for (NotificationRecipient recipient : recipientsPart) { if (recipientsPreview.size() < recipientsPreviewSize) { recipientsPreview.add(recipient.getTitle()); @@ -258,11 +247,23 @@ public class NotificationController extends BaseController { } recipientsCountByTarget.put(target.getName(), recipientsCount); } - preview.setRecipientsPreview(recipientsPreview); preview.setRecipientsCountByTarget(recipientsCountByTarget); preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum()); + NotificationProcessingContext ctx = NotificationProcessingContext.builder() + .tenantId(user.getTenantId()) + .request(request) + .template(template) + .settings(null) + .build(); + Map processedTemplates = ctx.getDeliveryMethods().stream() + .collect(Collectors.toMap(m -> m, deliveryMethod -> { + NotificationTargetType targetType = NotificationTargetType.forDeliveryMethod(deliveryMethod); + return ctx.getProcessedTemplate(deliveryMethod, firstRecipient.get(targetType)); + })); + preview.setProcessedTemplates(processedTemplates); + return preview; } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 4fd6b93022..6374b65710 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -71,7 +71,7 @@ public class NotificationTargetController extends BaseController { private final NotificationTargetService notificationTargetService; @ApiOperation(value = "Save notification target (saveNotificationTarget)", - notes = "Create or update notification target.\n\n" + + notes = "Create or update notification target." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PostMapping("/target") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index b4fba04212..fa7e973843 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -130,7 +130,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } if (ruleId == null) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { - throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing"); + throw new IllegalArgumentException("Recipients for " + deliveryMethod.getName() + " delivery method not chosen"); } } }); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 9dd34296ef..f6a296ab7e 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.notification.targets.platform.Customer import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; @@ -49,7 +50,6 @@ import org.thingsboard.server.common.data.notification.template.SlackDeliveryMet import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -226,13 +226,13 @@ public class NotificationApiTest extends AbstractNotificationApiTest { NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), notificationText, 5); assertThat(notificationRequest.getStatus()).isEqualTo(NotificationRequestStatus.SCHEDULED); await().atLeast(4, TimeUnit.SECONDS) - .atMost(6, TimeUnit.SECONDS) + .atMost(15, TimeUnit.SECONDS) .until(() -> wsClient.getLastMsg() != null); Notification delayedNotification = wsClient.getLastDataUpdate().getUpdate(); assertThat(delayedNotification).extracting(Notification::getText).isEqualTo(notificationText); assertThat(delayedNotification.getCreatedTime() - notificationRequest.getCreatedTime()) - .isCloseTo(TimeUnit.SECONDS.toMillis(5), Offset.offset(500L)); + .isCloseTo(TimeUnit.SECONDS.toMillis(5), Offset.offset(10000L)); assertThat(findNotificationRequest(notificationRequest.getId()).getStatus()).isEqualTo(NotificationRequestStatus.SENT); } @@ -324,16 +324,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Test public void testNotificationRequestPreview() throws Exception { - NotificationTarget target1 = new NotificationTarget(); - target1.setName("Me"); - PlatformUsersNotificationTargetConfig target1Config = new PlatformUsersNotificationTargetConfig(); + NotificationTarget tenantAdminTarget = new NotificationTarget(); + tenantAdminTarget.setName("Me"); + PlatformUsersNotificationTargetConfig tenantAdminTargetConfig = new PlatformUsersNotificationTargetConfig(); UserListFilter userListFilter = new UserListFilter(); - userListFilter.setUsersIds(DaoUtil.toUUIDs(List.of(tenantAdminUserId))); - target1Config.setUsersFilter(userListFilter); - target1.setConfiguration(target1Config); - target1 = saveNotificationTarget(target1); + userListFilter.setUsersIds(List.of(tenantAdminUserId.getId())); + tenantAdminTargetConfig.setUsersFilter(userListFilter); + tenantAdminTarget.setConfiguration(tenantAdminTargetConfig); + tenantAdminTarget = saveNotificationTarget(tenantAdminTarget); List recipients = new ArrayList<>(); recipients.add(TENANT_ADMIN_EMAIL); + String firstRecipientEmail = TENANT_ADMIN_EMAIL; createDifferentCustomer(); loginTenantAdmin(); @@ -347,21 +348,32 @@ public class NotificationApiTest extends AbstractNotificationApiTest { customerUser = createUser(customerUser, "12345678"); recipients.add(customerUser.getEmail()); } - NotificationTarget target2 = new NotificationTarget(); - target2.setName("Other customer users"); - PlatformUsersNotificationTargetConfig target2Config = new PlatformUsersNotificationTargetConfig(); + NotificationTarget customerUsersTarget = new NotificationTarget(); + customerUsersTarget.setName("Other customer users"); + PlatformUsersNotificationTargetConfig customerUsersTargetConfig = new PlatformUsersNotificationTargetConfig(); CustomerUsersFilter customerUsersFilter = new CustomerUsersFilter(); customerUsersFilter.setCustomerId(differentCustomerId.getId()); - target2Config.setUsersFilter(customerUsersFilter); - target2.setConfiguration(target2Config); - target2 = saveNotificationTarget(target2); - + customerUsersTargetConfig.setUsersFilter(customerUsersFilter); + customerUsersTarget.setConfiguration(customerUsersTargetConfig); + customerUsersTarget = saveNotificationTarget(customerUsersTarget); + + NotificationTarget slackTarget = new NotificationTarget(); + slackTarget.setName("Slack user"); + SlackNotificationTargetConfig slackTargetConfig = new SlackNotificationTargetConfig(); + slackTargetConfig.setConversationType(SlackConversationType.DIRECT); + SlackConversation slackConversation = new SlackConversation(); + slackConversation.setId("U1234567"); + slackConversation.setTitle("@jdoe (John Doe)"); + slackConversation.setWholeName("John Doe"); + slackTargetConfig.setConversation(slackConversation); + slackTarget.setConfiguration(slackTargetConfig); + slackTarget = saveNotificationTarget(slackTarget); + recipients.add(slackConversation.getTitle()); NotificationTemplate notificationTemplate = new NotificationTemplate(); notificationTemplate.setNotificationType(NotificationType.GENERAL); notificationTemplate.setName("Test template"); - String requestorEmail = TENANT_ADMIN_EMAIL; NotificationTemplateConfig templateConfig = new NotificationTemplateConfig(); HashMap templates = new HashMap<>(); templateConfig.setDeliveryMethodsTemplates(templates); @@ -369,69 +381,59 @@ public class NotificationApiTest extends AbstractNotificationApiTest { WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate(); webNotificationTemplate.setEnabled(true); - webNotificationTemplate.setBody("Message for WEB: ${recipientEmail} ${unknownParam}"); - webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}"); + webNotificationTemplate.setSubject("WEB SUBJECT: ${recipientEmail}"); + webNotificationTemplate.setBody("WEB: ${recipientEmail} ${unknownParam}"); templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate); SmsDeliveryMethodNotificationTemplate smsNotificationTemplate = new SmsDeliveryMethodNotificationTemplate(); smsNotificationTemplate.setEnabled(true); - smsNotificationTemplate.setBody("Message for SMS: ${recipientEmail}"); + smsNotificationTemplate.setBody("SMS: ${recipientEmail}"); templates.put(NotificationDeliveryMethod.SMS, smsNotificationTemplate); EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); emailNotificationTemplate.setEnabled(true); - emailNotificationTemplate.setSubject("Subject for EMAIL: ${recipientEmail}"); - emailNotificationTemplate.setBody("Message for EMAIL: ${recipientEmail}"); + emailNotificationTemplate.setSubject("EMAIL SUBJECT: ${recipientEmail}"); + emailNotificationTemplate.setBody("EMAIL: ${recipientEmail}"); templates.put(NotificationDeliveryMethod.EMAIL, emailNotificationTemplate); SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate(); slackNotificationTemplate.setEnabled(true); - slackNotificationTemplate.setBody("Message for SLACK: ${recipientEmail}"); + slackNotificationTemplate.setBody("SLACK: ${recipientFirstName} ${recipientLastName}"); templates.put(NotificationDeliveryMethod.SLACK, slackNotificationTemplate); notificationTemplate = saveNotificationTemplate(notificationTemplate); - NotificationRequest notificationRequest = new NotificationRequest(); - notificationRequest.setTargets(List.of(target1.getUuidId(), target2.getUuidId())); + notificationRequest.setTargets(List.of(tenantAdminTarget.getUuidId(), customerUsersTarget.getUuidId(), slackTarget.getUuidId())); notificationRequest.setTemplateId(notificationTemplate.getId()); notificationRequest.setAdditionalConfig(new NotificationRequestConfig()); NotificationRequestPreview preview = doPost("/api/notification/request/preview", notificationRequest, NotificationRequestPreview.class); - assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1); - assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount); - assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount); + assertThat(preview.getRecipientsCountByTarget().get(tenantAdminTarget.getName())).isEqualTo(1); + assertThat(preview.getRecipientsCountByTarget().get(customerUsersTarget.getName())).isEqualTo(customerUsersCount); + assertThat(preview.getRecipientsCountByTarget().get(slackTarget.getName())).isEqualTo(1); + + assertThat(preview.getTotalRecipientsCount()).isEqualTo(2 + customerUsersCount); assertThat(preview.getRecipientsPreview()).containsAll(recipients); Map processedTemplates = preview.getProcessedTemplates(); assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class)) .satisfies(template -> { - assertThat(template.getBody()) - .startsWith("Message for WEB") - .endsWith(requestorEmail + " ${unknownParam}"); - assertThat(template.getSubject()) - .startsWith("Subject for WEB") - .endsWith(requestorEmail); + assertThat(template.getSubject()).isEqualTo("WEB SUBJECT: " + firstRecipientEmail); + assertThat(template.getBody()).isEqualTo("WEB: " + firstRecipientEmail + " ${unknownParam}"); }); assertThat(processedTemplates.get(NotificationDeliveryMethod.SMS)).asInstanceOf(type(SmsDeliveryMethodNotificationTemplate.class)) .satisfies(template -> { - assertThat(template.getBody()) - .startsWith("Message for SMS") - .endsWith(requestorEmail); + assertThat(template.getBody()).isEqualTo("SMS: " + firstRecipientEmail); }); assertThat(processedTemplates.get(NotificationDeliveryMethod.EMAIL)).asInstanceOf(type(EmailDeliveryMethodNotificationTemplate.class)) .satisfies(template -> { - assertThat(template.getBody()) - .startsWith("Message for EMAIL") - .endsWith(requestorEmail); - assertThat(template.getSubject()) - .startsWith("Subject for EMAIL") - .endsWith(requestorEmail); + assertThat(template.getSubject()).isEqualTo("EMAIL SUBJECT: " + firstRecipientEmail); + assertThat(template.getBody()).isEqualTo("EMAIL: " + firstRecipientEmail); }); assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class)) .satisfies(template -> { - assertThat(template.getBody()) - .isEqualTo("Message for SLACK: ${recipientEmail}"); // ${recipientEmail} should not be processed + assertThat(template.getBody()).isEqualTo("SLACK: John Doe"); }); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java index 0c2441e37d..1254654ecf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java @@ -19,6 +19,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import java.util.Arrays; import java.util.Set; @RequiredArgsConstructor @@ -30,4 +31,10 @@ public enum NotificationTargetType { @Getter private final Set supportedDeliveryMethods; + public static NotificationTargetType forDeliveryMethod(NotificationDeliveryMethod deliveryMethod) { + return Arrays.stream(values()) + .filter(targetType -> targetType.getSupportedDeliveryMethods().contains(deliveryMethod)) + .findFirst().orElse(null); + } + } From dae9a422a15e02999ca6447d3c3aa0375a3f37d8 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 12 Apr 2023 16:44:24 +0300 Subject: [PATCH 063/168] Fix entities ids replaced with tenant id in dashboard config when importing --- .../impl/BaseEntityImportService.java | 46 +++++++--------- .../impl/DashboardImportService.java | 4 +- .../DefaultEntitiesVersionControlService.java | 6 +- .../sync/ie/ExportImportServiceSqlTest.java | 55 ++++++++++++------- monitoring/src/main/conf/logback.xml | 7 ++- 5 files changed, 66 insertions(+), 52 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java index f72ac8a11f..610f1ee0b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java @@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; -import org.springframework.transaction.annotation.Transactional; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; @@ -87,7 +86,6 @@ public abstract class BaseEntityImportService importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException { EntityImportResult importResult = new EntityImportResult<>(); @@ -336,27 +334,37 @@ public abstract class BaseEntityImportService externalIdOpt = buildEntityId(entityType, externalUuid); - if (!externalIdOpt.isPresent()) { + Optional externalId = buildEntityId(entityType, externalUuid); + if (externalId.isEmpty()) { continue; } - EntityId internalId = ctx.getInternalId(externalIdOpt.get()); + EntityId internalId = ctx.getInternalId(externalId.get()); if (internalId != null) { return Optional.of(internalId); } } if (fetchAllUUIDs) { - for (EntityType entityType : hints) { - Optional internalId = lookupInDb(externalUuid, entityType); - if (internalId.isPresent()) return internalId; - } + Set processLast = Set.of(EntityType.TENANT); + List entityTypes = new ArrayList<>(hints); for (EntityType entityType : EntityType.values()) { - if (hints.contains(entityType)) { + if (!hints.contains(entityType) && !processLast.contains(entityType)) { + entityTypes.add(entityType); + } + } + entityTypes.addAll(processLast); + + for (EntityType entityType : entityTypes) { + Optional externalId = buildEntityId(entityType, externalUuid); + if (externalId.isEmpty() || ctx.isNotFound(externalId.get())) { continue; } - Optional internalId = lookupInDb(externalUuid, entityType); - if (internalId.isPresent()) return internalId; + EntityId internalId = getInternalId(externalId.get(), false); + if (internalId != null) { + return Optional.of(internalId); + } else { + ctx.registerNotFound(externalId.get()); + } } } @@ -364,20 +372,6 @@ public abstract class BaseEntityImportService lookupInDb(UUID externalUuid, EntityType entityType) { - Optional externalIdOpt = buildEntityId(entityType, externalUuid); - if (externalIdOpt.isEmpty() || ctx.isNotFound(externalIdOpt.get())) { - return Optional.empty(); - } - EntityId internalId = getInternalId(externalIdOpt.get(), false); - if (internalId != null) { - return Optional.of(internalId); - } else { - ctx.registerNotFound(externalIdOpt.get()); - } - return Optional.empty(); - } - private Optional buildEntityId(EntityType entityType, UUID externalUuid) { try { return Optional.of(EntityIdFactory.getByTypeAndUuid(entityType, externalUuid)); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java index 611b536808..250026c71c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java @@ -65,10 +65,10 @@ public class DashboardImportService extends BaseEntityImportService exportData, IdProvider idProvider) { for (JsonNode entityAlias : dashboard.getEntityAliasesConfig()) { - replaceIdsRecursively(ctx, idProvider, entityAlias, Collections.emptySet(), HINTS); + replaceIdsRecursively(ctx, idProvider, entityAlias, Set.of("id"), HINTS); } for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { - replaceIdsRecursively(ctx, idProvider, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.singleton("id"), HINTS); + replaceIdsRecursively(ctx, idProvider, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Set.of("id"), HINTS); } return dashboard; } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index 8ffd4796fe..0e08ee9c48 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; @@ -69,6 +68,7 @@ import org.thingsboard.server.common.data.sync.vc.request.load.EntityTypeVersion import org.thingsboard.server.common.data.sync.vc.request.load.SingleEntityVersionLoadRequest; import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadConfig; import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; +import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; @@ -259,7 +259,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont return gitServiceQueue.listEntitiesAtVersion(tenantId, versionId); } - @SuppressWarnings({"UnstableApiUsage", "rawtypes"}) + @SuppressWarnings({"rawtypes"}) @Override public UUID loadEntitiesVersion(User user, VersionLoadRequest request) throws Exception { EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), user, request.getVersionId()); @@ -402,7 +402,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>()) .add(importResult.getSavedEntity().getId()); } - log.debug("Imported {} pack for tenant {}", entityType, ctx.getTenantId()); + log.debug("Imported {} pack ({}) for tenant {}", entityType, entityDataList.size(), ctx.getTenantId()); offset += limit; } while (entityDataList.size() == limit); } diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java index 26e8b3e92a..e493916b8a 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java @@ -68,7 +68,6 @@ import org.thingsboard.server.service.ota.OtaPackageStateService; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -254,21 +253,25 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest { Asset asset1 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 1"); Asset asset2 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 2"); Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); + DeviceProfile existingDeviceProfile = createDeviceProfile(tenantId2, null, null, "Existing"); + String aliasId = "23c4185d-1497-9457-30b2-6d91e69a5b2c"; + String unknownUuid = "ea0dc8b0-3d85-11ed-9200-77fc04fa14fa"; String entityAliases = "{\n" + - "\t\"23c4185d-1497-9457-30b2-6d91e69a5b2c\": {\n" + - "\t\t\"alias\": \"assets\",\n" + - "\t\t\"filter\": {\n" + - "\t\t\t\"entityList\": [\n" + - "\t\t\t\t\"" + asset1.getId().toString() + "\",\n" + - "\t\t\t\t\"" + asset2.getId().toString() + "\"\n" + - "\t\t\t],\n" + - "\t\t\t\"entityType\": \"ASSET\",\n" + - "\t\t\t\"resolveMultiple\": true,\n" + - "\t\t\t\"type\": \"entityList\"\n" + - "\t\t},\n" + - "\t\t\"id\": \"23c4185d-1497-9457-30b2-6d91e69a5b2c\"\n" + - "\t}\n" + + "\"" + aliasId + "\": {\n" + + "\"alias\": \"assets\",\n" + + "\"filter\": {\n" + + "\"entityList\": [\n" + + "\"" + asset1.getId().toString() + "\",\n" + + "\"" + asset2.getId().toString() + "\",\n" + + "\"" + tenantId1.getId().toString() + "\",\n" + + "\"" + existingDeviceProfile.getId().toString() + "\",\n" + + "\"" + unknownUuid + "\"\n" + + "],\n" + + "\"resolveMultiple\": true\n" + + "},\n" + + "\"id\": \"" + aliasId + "\"\n" + + "}\n" + "}"; ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode(); dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases)); @@ -287,11 +290,23 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest { Asset importedAsset2 = importEntity(tenantAdmin2, asset2ExportData).getSavedEntity(); Dashboard importedDashboard = importEntity(tenantAdmin2, dashboardExportData).getSavedEntity(); - Set entityAliasEntitiesIds = Streams.stream(importedDashboard.getConfiguration() - .get("entityAliases").elements().next().get("filter").get("entityList").elements()) - .map(JsonNode::asText).collect(Collectors.toSet()); - assertThat(entityAliasEntitiesIds).doesNotContain(asset1.getId().toString(), asset2.getId().toString()); - assertThat(entityAliasEntitiesIds).contains(importedAsset1.getId().toString(), importedAsset2.getId().toString()); + Map.Entry entityAlias = importedDashboard.getConfiguration().get("entityAliases").fields().next(); + assertThat(entityAlias.getKey()).isEqualTo(aliasId); + assertThat(entityAlias.getValue().get("id").asText()).isEqualTo(aliasId); + + List aliasEntitiesIds = Streams.stream(entityAlias.getValue().get("filter").get("entityList").elements()) + .map(JsonNode::asText).collect(Collectors.toList()); + assertThat(aliasEntitiesIds).size().isEqualTo(5); + assertThat(aliasEntitiesIds).element(0).as("external asset 1 was replaced with imported one") + .isEqualTo(importedAsset1.getId().toString()); + assertThat(aliasEntitiesIds).element(1).as("external asset 2 was replaced with imported one") + .isEqualTo(importedAsset2.getId().toString()); + assertThat(aliasEntitiesIds).element(2).as("external tenant id was replaced with new tenant id") + .isEqualTo(tenantId2.toString()); + assertThat(aliasEntitiesIds).element(3).as("existing device profile id was left as is") + .isEqualTo(existingDeviceProfile.getId().toString()); + assertThat(aliasEntitiesIds).element(4).as("unresolved uuid was replaced with tenant id") + .isEqualTo(tenantId2.toString()); } @@ -469,7 +484,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest { Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device 1"); Map entitiesExportData = Stream.of(customer.getId(), asset.getId(), device.getId(), - ruleChain.getId(), dashboard.getId(), assetProfile.getId(), deviceProfile.getId()) + ruleChain.getId(), dashboard.getId(), assetProfile.getId(), deviceProfile.getId()) .map(entityId -> { try { return exportEntity(tenantAdmin1, entityId, EntityExportSettings.builder() diff --git a/monitoring/src/main/conf/logback.xml b/monitoring/src/main/conf/logback.xml index 58e49c7639..3c1e1382b7 100644 --- a/monitoring/src/main/conf/logback.xml +++ b/monitoring/src/main/conf/logback.xml @@ -33,14 +33,19 @@ %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + - + From fbeb56cf70a27d2bcc8cdf2867a2e90b332025ec Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 12 Apr 2023 17:50:26 +0300 Subject: [PATCH 064/168] Refactor after review and add tests for extracting by regex --- .../device/DeviceProvisionServiceImpl.java | 31 +- .../transport/DefaultTransportApiService.java | 6 +- .../provision/DeviceProvisionServiceTest.java | 267 ++++++++++++++++++ .../DefaultTransportApiServiceTest.java | 9 +- .../x509ChainProvisionTest.pem | 0 .../dao/device/DeviceProvisionService.java | 2 +- .../validator/DeviceProfileDataValidator.java | 4 +- .../x509-chain-regex-examples.md | 26 +- 8 files changed, 312 insertions(+), 33 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java rename application/src/test/resources/{mqtt => provision}/x509ChainProvisionTest.pem (100%) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 15198d210f..6d1156986d 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -100,7 +100,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } @Override - public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) { + public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) throws ProvisionFailedException { if (targetProfile == null) { throw new ProvisionFailedException("Device profile is not specified!"); } @@ -110,9 +110,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); String certificateRegEx = configuration.getCertificateRegExPattern(); - String deviceName = extractDeviceNameFromCertificateCNByRegEx(targetProfile, certificateValue, certificateRegEx); + String commonName = getCNFromX509Certificate(certificateValue); + String deviceName = extractDeviceNameFromCNByRegEx(targetProfile, commonName, certificateRegEx); if (StringUtils.isBlank(deviceName)) { - log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]", certificateRegEx, certificateValue); + log.warn("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", targetProfile.getTenantId(), targetProfile.getId(), certificateRegEx, certificateValue); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } provisionRequest.setDeviceName(deviceName); @@ -120,7 +121,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); - if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + if (DeviceCredentialsType.X509_CERTIFICATE.equals(deviceCredentials.getCredentialsType())) { String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); @@ -295,21 +296,25 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); } - private String extractDeviceNameFromCertificateCNByRegEx(DeviceProfile profile, String x509Value, String regex) { + private String getCNFromX509Certificate(String x509Value) { try { - String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); - log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); + return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); + } catch (Exception e) { + return null; + } + } + + public String extractDeviceNameFromCNByRegEx(DeviceProfile profile, String commonName, String regex) { + try { + log.trace("Extract device name from CN [{}] by regex pattern [{}]", commonName, regex); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commonName); if (matcher.find()) { return matcher.group(1); - } else { - return null; } - } catch (Exception ignored) { - log.trace("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", profile.getTenantId(), profile.getId(), regex, x509Value); - return null; - } + } catch (Exception ignored) {} + log.trace("[{}][{}] Failed to match device name using [{}] from CN: [{}]", profile.getTenantId(), profile.getId(), regex, commonName); + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index b99c9c2509..57aa1e735e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; @@ -113,7 +114,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID; @@ -246,7 +246,7 @@ public class DefaultTransportApiService implements TransportApiService { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); - if (deviceProfile != null && X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) { + if (deviceProfile != null && DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) { String updatedDeviceProvisionSecret = chain.get(0); ProvisionRequest provisionRequest = createProvisionRequest(updatedDeviceProvisionSecret); try { @@ -259,7 +259,7 @@ public class DefaultTransportApiService implements TransportApiService { return getEmptyTransportApiResponseFuture(); } } else if (deviceProfile != null) { - log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getId(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); + log.warn("[{}][{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getTenantId(), deviceProfile.getId(), DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); } } return getEmptyTransportApiResponseFuture(); diff --git a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java new file mode 100644 index 0000000000..0ccae57e0f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java @@ -0,0 +1,267 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.device.provision; + + +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.audit.AuditLogService; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.service.device.DeviceProvisionServiceImpl;;import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = DeviceProvisionServiceImpl.class) +public class DeviceProvisionServiceTest { + + + @MockBean + protected TbQueueProducerProvider producerProvider; + @MockBean + protected TbQueueProducer> ruleEngineMsgProducer; + @MockBean + protected TbClusterService clusterService; + @MockBean + protected DeviceProfileService deviceProfileService; + @MockBean + protected DeviceService deviceService; + @MockBean + protected DeviceCredentialsService deviceCredentialsService; + @MockBean + protected AttributesService attributesService; + @MockBean + protected AuditLogService auditLogService; + @MockBean + protected PartitionService partitionService; + @SpyBean + DeviceProvisionServiceImpl service; + + private String[] chain; + + @Before + public void setUp() { + String filePath = "src/test/resources/provision/x509ChainProvisionTest.pem"; + try { + String certificateChain = Files.readString(Paths.get(filePath)); + certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain); + chain = fetchLeafCertificateFromChain(certificateChain); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + @Test + public void provisionDeviceViaX509Certificate() { + var tenant = createTenant(); + var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], true); + + var device = createDevice(tenant.getId(), deviceProfile.getId()); + when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device); + + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); + when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + + ProvisionResponse response = service.provisionDeviceViaX509Chain(deviceProfile, createProvisionRequest(chain[0])); + + verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + + Assertions.assertThat(response.getResponseStatus()).isEqualTo(ProvisionResponseStatus.SUCCESS); + Assertions.assertThat(response.getDeviceCredentials()).isEqualTo(deviceCredentials); + } + + @Test + public void provisionDeviceWithIncorrectConfiguration() { + var tenant = createTenant(); + var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], false); + + Assertions.assertThatThrownBy(() -> + service.provisionDeviceViaX509Chain(deviceProfile, createProvisionRequest(chain[0]))) + .isInstanceOf(ProvisionFailedException.class); + + verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); + } + + @Test + public void matchDeviceNameFromX509CNCertificateByRegex() { + var tenant = createTenant(); + var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], true); + X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + String CN = getCNFromX509Certificate(chain[0]); + String deviceName = service.extractDeviceNameFromCNByRegEx(deviceProfile, CN, configuration.getCertificateRegExPattern()); + + Assertions.assertThat(deviceName).isNotBlank(); + Assertions.assertThat(deviceName).isEqualTo("deviceCertificate"); + } + + @Test + public void matchDeviceNameFromCNByRegex() { + var CN = "DeviceA.company.com"; + var regex = "(.*)\\.company.com"; + var result = service.extractDeviceNameFromCNByRegEx(null, CN, regex); + Assertions.assertThat(result).isNotBlank(); + Assertions.assertThat(result).isEqualTo("DeviceA"); + + CN = "DeviceA@company.com"; + regex = "(.*)@company.com"; + result = service.extractDeviceNameFromCNByRegEx(null, CN, regex); + Assertions.assertThat(result).isNotBlank(); + Assertions.assertThat(result).isEqualTo("DeviceA"); + + CN = "prefixDeviceAsuffix@company.com"; + regex = "prefix(.*)suffix@company.com"; + result = service.extractDeviceNameFromCNByRegEx(null, CN, regex); + Assertions.assertThat(result).isNotBlank(); + Assertions.assertThat(result).isEqualTo("DeviceA"); + + CN = "prefixDeviceAsufix@company.com"; + regex = "prefix(.*)sufix@company.com"; + result = service.extractDeviceNameFromCNByRegEx(null, CN, regex); + Assertions.assertThat(result).isNotBlank(); + Assertions.assertThat(result).isEqualTo("DeviceA"); + + CN = "region.DeviceA.220423@company.com"; + regex = "\\D+\\.(.*)\\.\\d+@company.com"; + result = service.extractDeviceNameFromCNByRegEx(null, CN, regex); + Assertions.assertThat(result).isNotBlank(); + Assertions.assertThat(result).isEqualTo("DeviceA"); + } + + private DeviceProfile createDeviceProfile(TenantId tenantId, String certificateValue, boolean isAllowToCreateNewDevices) { + X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); + provision.setProvisionDeviceSecret(certificateValue); + provision.setCertificateRegExPattern("([^@]+)"); + provision.setAllowCreateNewDevicesByX509Certificate(isAllowToCreateNewDevices); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setProvisionConfiguration(provision); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(new DeviceProfileId(UUID.randomUUID())); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setProvisionDeviceKey(EncryptionUtil.getSha3Hash(certificateValue)); + deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); + deviceProfile.setTenantId(tenantId); + return deviceProfile; + } + + private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId) { + Device device = new Device(); + device.setTenantId(tenantId); + device.setId(new DeviceId(UUID.randomUUID())); + device.setDeviceProfileId(deviceProfileId); + device.setCustomerId(new CustomerId(UUID.randomUUID())); + return device; + } + + private Tenant createTenant() { + Tenant tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + return tenant; + } + + private DeviceCredentials createDeviceCredentials(String certificateValue, DeviceId deviceId) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setDeviceId(deviceId); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash(certificateValue)); + deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + return deviceCredentials; + } + + private ProvisionRequest createProvisionRequest(String certificateValue) { + return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, + new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), + null); + } + + public static String certTrimNewLinesForChainInDeviceProfile(String input) { + return input.replaceAll("\n", "") + .replaceAll("\r", "") + .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n") + .trim(); + } + + private String[] fetchLeafCertificateFromChain(String value) { + List chain = new ArrayList<>(); + String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) { + chain.add(matcher.group(0)); + } + return chain.toArray(new String[0]); + } + + private String getCNFromX509Certificate(String x509Value) { + try { + return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); + } catch (Exception e) { + return null; + } + } +} diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index 21a03c6167..9687225bd1 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -109,7 +109,8 @@ public class DefaultTransportApiServiceTest { @Before public void setUp() { - String filePath = "src/test/resources/mqtt/x509ChainProvisionTest.pem"; + + String filePath = "src/test/resources/provision/x509ChainProvisionTest.pem"; try { certificateChain = Files.readString(Paths.get(filePath)); certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain); @@ -120,7 +121,7 @@ public class DefaultTransportApiServiceTest { } @Test - public void validateExistingDeviceX509Certificate() { + public void validateExistingDeviceByX509CertificateStrategy() { var device = createDevice(); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); @@ -145,13 +146,13 @@ public class DefaultTransportApiServiceTest { when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); var provisionResponse = createProvisionResponse(deviceCredentials); - when(deviceProvisionService.provisionDevice(any())).thenReturn(provisionResponse); + when(deviceProvisionService.provisionDeviceViaX509Chain(any(), any())).thenReturn(provisionResponse); service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceProfileService, times(1)).findDeviceProfileByProvisionDeviceKey(any()); verify(deviceService, times(1)).findDeviceByIdAsync(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); - verify(deviceProvisionService, times(1)).provisionDevice(any()); + verify(deviceProvisionService, times(1)).provisionDeviceViaX509Chain(any(), any()); } private DeviceProfile createDeviceProfile(String certificateValue) { diff --git a/application/src/test/resources/mqtt/x509ChainProvisionTest.pem b/application/src/test/resources/provision/x509ChainProvisionTest.pem similarity index 100% rename from application/src/test/resources/mqtt/x509ChainProvisionTest.pem rename to application/src/test/resources/provision/x509ChainProvisionTest.pem diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index fa47ed0694..5fb6462c80 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -24,5 +24,5 @@ public interface DeviceProvisionService { ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; - ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest); + ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest) throws ProvisionFailedException; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index d092c34861..24e5ceb1b2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -408,8 +408,8 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator.* - matches any character (until line terminators) -
    **CN sample:** DeviceName\nAdditionalInfo -
    **Pattern matches:** DeviceName +The regular expression is required to extract device name from the X509 certificate's common name. +The regular expression syntax is based on Java [Pattern](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html). +You may also use this [resource](https://regex101.com/) to test your expressions but make sure you select Java 8 flavor. -* **Pattern:** ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol) -
    **CN sample:** DeviceName@AdditionalInfo -
    **Pattern matches:** DeviceName +* **Pattern:**(.*)\.company.com- matches any characters before the ".company.com". +
    **CN sample:**DeviceA.company.com +
    **Result:**DeviceA -* **Pattern:** [\w]*$ (equivalent to [a-zA-Z0-9_]\*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string -
    **CN sample:** AdditionalInfo2110#DeviceName_01 -
    **Pattern matches:** DeviceName_01 +* **Pattern:** (.*)@company.com- matches any characters before the "@company.com". +
    **CN sample:**DeviceA@company.com +
    **Result:**DeviceA -**Note:** Client will get error response in case regex is failed to match. +* **Pattern:** prefix(.*)suffix@company.com- matches characters between "prefix" and "suffix@company.com". +
    **CN sample:**prefixDeviceAsuffix@company.com +
    **Pattern matches:** DeviceA + +* **Pattern:** \\D+\\.(.*)\\.\\d+@company.com- matches characters between not digits prefix followed by period and sequence of digits with "@company.com" ending. +
    **CN sample:**region.DeviceA.220423@company.com +
    **Pattern matches:** DeviceA From bbde3b4b45d4b649dd502f81880d87dd189568e5 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 12 Apr 2023 18:49:40 +0300 Subject: [PATCH 065/168] Add trace logging fpr parsing CN and refactor extractByRegex to throw ProvisionFailedException --- .../service/device/DeviceProvisionServiceImpl.java | 13 +++++-------- application/src/main/resources/thingsboard.yml | 8 ++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 6d1156986d..beecebe2ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -110,12 +110,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); String certificateRegEx = configuration.getCertificateRegExPattern(); - String commonName = getCNFromX509Certificate(certificateValue); + String commonName = getCNFromX509Certificate(targetProfile, certificateValue); String deviceName = extractDeviceNameFromCNByRegEx(targetProfile, commonName, certificateRegEx); - if (StringUtils.isBlank(deviceName)) { - log.warn("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", targetProfile.getTenantId(), targetProfile.getId(), certificateRegEx, certificateValue); - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); - } provisionRequest.setDeviceName(deviceName); Device targetDevice = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); @@ -296,15 +292,16 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); } - private String getCNFromX509Certificate(String x509Value) { + private String getCNFromX509Certificate(DeviceProfile profile, String x509Value) { try { return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); } catch (Exception e) { + log.trace("[{}][{}] Failed to parse CN from X509 certificate {}", profile.getTenantId(), profile.getId(), x509Value); return null; } } - public String extractDeviceNameFromCNByRegEx(DeviceProfile profile, String commonName, String regex) { + public String extractDeviceNameFromCNByRegEx(DeviceProfile profile, String commonName, String regex) throws ProvisionFailedException { try { log.trace("Extract device name from CN [{}] by regex pattern [{}]", commonName, regex); Pattern pattern = Pattern.compile(regex); @@ -314,7 +311,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } } catch (Exception ignored) {} log.trace("[{}][{}] Failed to match device name using [{}] from CN: [{}]", profile.getTenantId(), profile.getId(), regex, commonName); - return null; + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 9f90b3457a..ee64e389b0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -746,7 +746,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:false}" + enabled: "${MQTT_SSL_ENABLED:true}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -760,11 +760,11 @@ transport: # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) - cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/x509/server.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:/home/developer/x509/server_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) From 579a793f4083e6b35d8ed076c94832491b94d30d Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 12 Apr 2023 18:54:39 +0300 Subject: [PATCH 066/168] Remove unnessessary yaml config --- application/src/main/resources/thingsboard.yml | 8 ++++---- .../device/provision/DeviceProvisionServiceTest.java | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ee64e389b0..9f90b3457a 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -746,7 +746,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:true}" + enabled: "${MQTT_SSL_ENABLED:false}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -760,11 +760,11 @@ transport: # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) - cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/x509/server.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:/home/developer/x509/server_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) diff --git a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java index 0ccae57e0f..388058eef4 100644 --- a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java @@ -74,7 +74,6 @@ import static org.mockito.Mockito.when; @ContextConfiguration(classes = DeviceProvisionServiceImpl.class) public class DeviceProvisionServiceTest { - @MockBean protected TbQueueProducerProvider producerProvider; @MockBean From e926854911524d7063ecfecd65cd7fd68028a8fb Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 13 Apr 2023 12:53:59 +0300 Subject: [PATCH 067/168] NoXss and length validation for notification entities --- .../server/controller/BaseController.java | 22 ++++++---- .../DefaultNotificationCenter.java | 18 +++++--- .../channels/EmailNotificationChannel.java | 5 +-- .../channels/NotificationChannel.java | 2 +- .../channels/SlackNotificationChannel.java | 10 +++-- .../channels/SmsNotificationChannel.java | 6 ++- .../notification/rule/NotificationRule.java | 2 + .../targets/NotificationTarget.java | 2 + .../targets/NotificationTargetConfig.java | 2 + ...ailDeliveryMethodNotificationTemplate.java | 4 ++ .../template/NotificationTemplate.java | 2 + .../template/NotificationText.java | 30 ------------- ...ackDeliveryMethodNotificationTemplate.java | 7 +++ ...SmsDeliveryMethodNotificationTemplate.java | 9 ++++ ...WebDeliveryMethodNotificationTemplate.java | 15 +++++++ .../server/common/data/validation/Length.java | 4 +- .../server/common/data/validation/NoXss.java | 6 ++- .../dao/service/ConstraintValidator.java | 44 +++++++++++-------- 18 files changed, 115 insertions(+), 75 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 34f065d500..8a892b91a9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -103,7 +103,6 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.settings.UserDashboardAction; import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -130,12 +129,12 @@ import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; -import org.thingsboard.server.dao.user.UserSettingsService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; @@ -164,7 +163,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; +import javax.validation.ConstraintViolation; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -395,16 +396,19 @@ public abstract class BaseController { * Handles validation error for controller method arguments annotated with @{@link javax.validation.Valid} * */ @ExceptionHandler(MethodArgumentNotValidException.class) - public void handleValidationError(MethodArgumentNotValidException e, HttpServletResponse response) { - String errorMessage = "Validation error: " + e.getFieldErrors().stream() + public void handleValidationError(MethodArgumentNotValidException validationError, HttpServletResponse response) { + List> constraintsViolations = validationError.getFieldErrors().stream() .map(fieldError -> { - String property = fieldError.getField(); - if (property.equals("valid") || StringUtils.endsWith(property, ".valid")) { // when custom @AssertTrue is used - property = ""; + try { + return (ConstraintViolation) fieldError.unwrap(ConstraintViolation.class); + } catch (Exception e) { + log.warn("FieldError source is not of type ConstraintViolation"); + return null; // should not happen } - return (!property.isEmpty() ? (property + " ") : "") + fieldError.getDefaultMessage(); }) - .collect(Collectors.joining(", ")); + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String errorMessage = "Validation error: " + ConstraintValidator.getErrorMessage(constraintsViolations); ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS); handleControllerException(thingsboardException, response); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index fa7e973843..d297f70a7f 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -125,8 +125,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple NotificationRuleId ruleId = request.getRuleId(); notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { if (!template.isEnabled()) return; - if (!channels.get(deliveryMethod).check(tenantId)) { - throw new IllegalArgumentException("Unable to send notification via " + deliveryMethod.getName() + ": not configured or not working"); + try { + channels.get(deliveryMethod).check(tenantId); + } catch (Exception e) { + throw new IllegalArgumentException(e.getMessage()); } if (ruleId == null) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { @@ -341,14 +343,20 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Override public Set getAvailableDeliveryMethods(TenantId tenantId) { return channels.values().stream() - .filter(channel -> channel.check(tenantId)) + .filter(channel -> { + try { + channel.check(tenantId); + return true; + } catch (Exception e) { + return false; + } + }) .map(NotificationChannel::getDeliveryMethod) .collect(Collectors.toSet()); } @Override - public boolean check(TenantId tenantId) { - return true; + public void check(TenantId tenantId) throws Exception { } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java index 61e4c528dc..f5bbc57954 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -48,12 +48,11 @@ public class EmailNotificationChannel implements NotificationChannel sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx); - boolean check(TenantId tenantId); + void check(TenantId tenantId) throws Exception; NotificationDeliveryMethod getDeliveryMethod(); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java index 7ff48d4cb9..46afbd7270 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -22,12 +22,12 @@ import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; -import org.thingsboard.server.dao.notification.NotificationSettingsService; -import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; +import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; +import org.thingsboard.server.service.notification.NotificationProcessingContext; @Component @RequiredArgsConstructor @@ -47,9 +47,11 @@ public class SlackNotificationChannel implements NotificationChannel implements Ha private TenantId tenantId; @NotBlank @NoXss + @Length(max = 255, message = "cannot be longer than 255 chars") private String name; @NotNull private NotificationTemplateId templateId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 9f267a514d..9a2f9a5306 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; @@ -35,6 +36,7 @@ public class NotificationTarget extends BaseData implement private TenantId tenantId; @NotBlank @NoXss + @Length(max = 255, message = "cannot be longer than 255 chars") private String name; @NotNull @Valid diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java index 2333062f65..231e419fb0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Data; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @JsonIgnoreProperties(ignoreUnknown = true) @@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss; public abstract class NotificationTargetConfig { @NoXss + @Length(max = 500, message = "cannot be longer than 500 chars") private String description; @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java index bf8aca7913..fe909a104e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java @@ -21,6 +21,8 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.constraints.NotEmpty; @@ -30,6 +32,8 @@ import javax.validation.constraints.NotEmpty; @ToString(callSuper = true) public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { + @NoXss(fieldName = "email subject") + @Length(fieldName = "email subject", max = 250, message = "cannot be longer than 250 chars") @NotEmpty private String subject; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java index 2eaff12a9b..71da2f862b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; @@ -36,6 +37,7 @@ public class NotificationTemplate extends BaseData imple private TenantId tenantId; @NoXss @NotEmpty + @Length(max = 255, message = "cannot be longer than 255 chars") private String name; @NoXss @NotNull diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java deleted file mode 100644 index 6cb0e673a5..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.notification.template; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class NotificationText { - - private String body; - private String subject; - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java index 735eb3660e..8457677308 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java @@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.NoXss; @Data @NoArgsConstructor @@ -31,6 +32,12 @@ public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotif super(other); } + @NoXss(fieldName = "Slack message") + @Override + public String getBody() { + return super.getBody(); + } + @Override public NotificationDeliveryMethod getMethod() { return NotificationDeliveryMethod.SLACK; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java index 55d2582284..7dc2e494f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java @@ -20,6 +20,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; @Data @NoArgsConstructor @@ -31,6 +33,13 @@ public class SmsDeliveryMethodNotificationTemplate extends DeliveryMethodNotific super(other); } + @NoXss(fieldName = "SMS message") + @Length(fieldName = "SMS message", max = 320, message = "cannot be longer than 320 chars") + @Override + public String getBody() { + return super.getBody(); + } + @Override public NotificationDeliveryMethod getMethod() { return NotificationDeliveryMethod.SMS; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java index ac1e0cfd8c..f26ed00568 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java @@ -25,6 +25,8 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.constraints.NotEmpty; import java.util.Optional; @@ -35,6 +37,8 @@ import java.util.Optional; @ToString(callSuper = true) public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { + @NoXss(fieldName = "web notification subject") + @Length(fieldName = "web notification subject", max = 150, message = "cannot be longer than 150 chars") @NotEmpty private String subject; private JsonNode additionalConfig; @@ -45,6 +49,15 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific this.additionalConfig = other.additionalConfig != null ? other.additionalConfig.deepCopy() : null; } + @NoXss(fieldName = "web notification message") + @Length(fieldName = "web notification message", max = 250, message = "cannot be longer than 250 chars") + @Override + public String getBody() { + return super.getBody(); + } + + @NoXss(fieldName = "web notification button text") + @Length(fieldName = "web notification button text", max = 50, message = "cannot be longer than 50 chars") @JsonIgnore public String getButtonText() { return getButtonConfigProperty("text"); @@ -57,6 +70,8 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific }); } + @NoXss(fieldName = "web notification button link") + @Length(fieldName = "web notification button link", max = 300, message = "cannot be longer than 300 chars") @JsonIgnore public String getButtonLink() { return getButtonConfigProperty("link"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java index 2b412d0ad7..cb7b9b1693 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java @@ -23,12 +23,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) @Constraint(validatedBy = {}) public @interface Length { String message() default "length must be equal or less than {max}"; - String fieldName(); + String fieldName() default ""; int max() default 255; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java index c99502ed7b..fb7b048f7e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) @Constraint(validatedBy = {}) public @interface NoXss { + String message() default "is malformed"; + String fieldName() default ""; + Class[] groups() default {}; Class[] payload() default {}; + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java index 41a618959a..b884de0094 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; import com.google.common.collect.Iterators; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.cfg.ConstraintMapping; @@ -29,11 +30,13 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.dao.exception.DataValidationException; -import javax.validation.Path; +import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.constraints.AssertTrue; -import java.util.List; +import javax.validation.metadata.ConstraintDescriptor; +import java.util.Collection; +import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -51,26 +54,31 @@ public class ConstraintValidator { } public static void validateFields(Object data, String errorPrefix) { - List constraintsViolations = getConstraintsViolations(data); + Set> constraintsViolations = fieldsValidator.validate(data); if (!constraintsViolations.isEmpty()) { - throw new DataValidationException(errorPrefix + String.join(", ", constraintsViolations)); + throw new DataValidationException(errorPrefix + getErrorMessage(constraintsViolations)); } } - public static List getConstraintsViolations(Object data) { - return fieldsValidator.validate(data).stream() - .map(constraintViolation -> { - String property; - if (constraintViolation.getConstraintDescriptor().getAttributes().containsKey("fieldName")) { - property = constraintViolation.getConstraintDescriptor().getAttributes().get("fieldName").toString(); - } else { - Path propertyPath = constraintViolation.getPropertyPath(); - property = Iterators.getLast(propertyPath.iterator()).toString(); - } - return property + " " + constraintViolation.getMessage(); - }) - .distinct() - .collect(Collectors.toList()); + public static String getErrorMessage(Collection> constraintsViolations) { + return constraintsViolations.stream() + .map(ConstraintValidator::getErrorMessage) + .distinct().sorted().collect(Collectors.joining(", ")); + } + + public static String getErrorMessage(ConstraintViolation constraintViolation) { + ConstraintDescriptor constraintDescriptor = constraintViolation.getConstraintDescriptor(); + String property = (String) constraintDescriptor.getAttributes().get("fieldName"); + if (StringUtils.isEmpty(property) && !(constraintDescriptor.getAnnotation() instanceof AssertTrue)) { + property = Iterators.getLast(constraintViolation.getPropertyPath().iterator()).toString(); + } + + String error = ""; + if (StringUtils.isNotEmpty(property)) { + error += property + " "; + } + error += constraintViolation.getMessage(); + return error; } private static void initializeValidators() { From 542d0dfe0c8899940a826bd9d9be87182731c968 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 13 Apr 2023 12:54:44 +0300 Subject: [PATCH 068/168] Don't send notifications to customers for default rules --- .../DefaultNotificationSettingsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 9438c79b62..8319468c04 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -170,8 +170,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS newAlarmRuleTriggerConfig.setAlarmSeverities(null); newAlarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED)); createRule(tenantId, "New alarm", newAlarmNotificationTemplate.getId(), newAlarmRuleTriggerConfig, - List.of(tenantAdmins.getId(), originatorEntityOwnerUsers.getId()), "Send notification to tenant admins and alarm's customer users " + - "when an alarm is created"); + List.of(tenantAdmins.getId()), "Send notification to tenant admins when an alarm is created"); NotificationTemplate alarmUpdateNotificationTemplate = createTemplate(tenantId, "Alarm update notification", NotificationType.ALARM, "Alarm '${alarmType}' - ${action}", @@ -182,8 +181,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS alarmRuleTriggerConfig.setAlarmSeverities(null); alarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED)); createRule(tenantId, "Alarm update", alarmUpdateNotificationTemplate.getId(), alarmRuleTriggerConfig, - List.of(tenantAdmins.getId(), originatorEntityOwnerUsers.getId()), "Send notification to tenant admins and alarm's customer users " + - "when any alarm is updated or cleared"); + List.of(tenantAdmins.getId()), "Send notification to tenant admins when any alarm is updated or cleared"); NotificationTemplate deviceActionNotificationTemplate = createTemplate(tenantId, "Device action notification", NotificationType.ENTITY_ACTION, "${entityType} was ${actionType}", @@ -195,8 +193,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS deviceActionRuleTriggerConfig.setUpdated(false); deviceActionRuleTriggerConfig.setDeleted(false); createRule(tenantId, "Device created", deviceActionNotificationTemplate.getId(), deviceActionRuleTriggerConfig, - List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + - "when device is created"); + List.of(tenantAdmins.getId()), "Send notification to tenant admins when device is created"); NotificationTemplate deviceActivityNotificationTemplate = createTemplate(tenantId, "Device activity notification", NotificationType.DEVICE_ACTIVITY, "Device '${deviceName}' became ${eventType}", @@ -207,8 +204,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS deviceActivityRuleTriggerConfig.setDeviceProfiles(null); deviceActivityRuleTriggerConfig.setNotifyOn(Set.of(DeviceEvent.ACTIVE, DeviceEvent.INACTIVE)); createRule(tenantId, "Device activity status change", deviceActivityNotificationTemplate.getId(), deviceActivityRuleTriggerConfig, - List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + - "when any device changes its activity state"); + List.of(tenantAdmins.getId()), "Send notification to tenant admins when any device changes its activity state"); NotificationTemplate alarmCommentNotificationTemplate = createTemplate(tenantId, "Alarm comment notification", NotificationType.ALARM_COMMENT, "Comment on '${alarmType}' alarm", @@ -221,8 +217,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS alarmCommentRuleTriggerConfig.setOnlyUserComments(true); alarmCommentRuleTriggerConfig.setNotifyOnCommentUpdate(false); createRule(tenantId, "Comment on active alarm", alarmCommentNotificationTemplate.getId(), alarmCommentRuleTriggerConfig, - List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + - "when comment is added by user on active alarm"); + List.of(tenantAdmins.getId()), "Send notification to tenant admins when comment is added by user on active alarm"); NotificationTemplate alarmAssignedNotificationTemplate = createTemplate(tenantId, "Alarm assigned notification", NotificationType.ALARM_ASSIGNMENT, "Alarm '${alarmType}' (${alarmSeverity}) was assigned to user", From 7b6f3ed4df034270e3c160888fdbde9c0e144f83 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 13 Apr 2023 18:20:37 +0300 Subject: [PATCH 069/168] Notification requests per rule rate limits --- .../limits/DefaultRateLimitService.java | 69 +++++++--- .../service/apiusage/limits/LimitedApi.java | 3 +- .../apiusage/limits/RateLimitService.java | 5 +- .../DefaultNotificationCenter.java | 21 ++- .../NotificationProcessingContext.java | 12 +- .../DefaultNotificationRuleProcessor.java | 10 +- .../DefaultEntitiesExportImportService.java | 4 +- .../src/main/resources/thingsboard.yml | 4 + .../notification/NotificationRuleApiTest.java | 123 +++++++++--------- .../NotificationTemplateApiTest.java | 10 -- .../DefaultTenantProfileConfiguration.java | 1 + ...enant-profile-configuration.component.html | 3 + ...-tenant-profile-configuration.component.ts | 1 + .../tenant/rate-limits/rate-limits.models.ts | 5 +- ui-ngx/src/app/shared/models/tenant.model.ts | 1 + .../assets/locale/locale.constant-en_US.json | 2 + 16 files changed, 165 insertions(+), 109 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java index e995a8bd1f..9a0282c840 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java @@ -15,54 +15,81 @@ */ package org.thingsboard.server.service.apiusage.limits; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import lombok.Data; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.PostConstruct; +import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor +@Slf4j public class DefaultRateLimitService implements RateLimitService { private final TbTenantProfileCache tenantProfileCache; + @Value("${cache.rateLimits.timeToLiveInMinutes:60}") + private int rateLimitsTtl; + @Value("${cache.rateLimits.maxSize:100000}") + private int rateLimitsCacheMaxSize; - private final Map> rateLimits = new ConcurrentHashMap<>(); + private Cache rateLimits; + + @PostConstruct + private void init() { + rateLimits = Caffeine.newBuilder() + .expireAfterAccess(rateLimitsTtl, TimeUnit.MINUTES) + .maximumSize(rateLimitsCacheMaxSize) + .build(); + } + + @Override + public boolean checkRateLimit(LimitedApi api, TenantId tenantId) { + return checkRateLimit(api, tenantId, tenantId); + } @Override - public boolean checkRateLimit(TenantId tenantId, LimitedApi api) { + public boolean checkRateLimit(LimitedApi api, TenantId tenantId, EntityId entityId) { if (tenantId.isSysTenantId()) { return true; } + RateLimitKey key = new RateLimitKey(api, entityId); + String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration() .map(api::getLimitConfig).orElse(null); - - Map rateLimits = this.rateLimits.get(api); if (StringUtils.isEmpty(rateLimitConfig)) { - if (rateLimits != null) { - rateLimits.remove(tenantId); - if (rateLimits.isEmpty()) { - this.rateLimits.remove(api); - } - } + rateLimits.invalidate(key); return true; } + log.trace("[{}] Checking rate limit for {} ({})", entityId, api, rateLimitConfig); - if (rateLimits == null) { - rateLimits = new ConcurrentHashMap<>(); - this.rateLimits.put(api, rateLimits); - } - TbRateLimits rateLimit = rateLimits.get(tenantId); - if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) { - rateLimit = new TbRateLimits(rateLimitConfig); - rateLimits.put(tenantId, rateLimit); + TbRateLimits rateLimit = rateLimits.asMap().compute(key, (k, limit) -> { + if (limit == null || !limit.getConfiguration().equals(rateLimitConfig)) { + limit = new TbRateLimits(rateLimitConfig); + log.trace("[{}] Created new rate limit bucket for {} ({})", entityId, api, rateLimitConfig); + } + return limit; + }); + boolean success = rateLimit.tryConsume(); + if (!success) { + log.debug("[{}] Rate limit exceeded for {} ({})", entityId, api, rateLimitConfig); } + return success; + } - return rateLimit.tryConsume(); + @Data(staticConstructor = "of") + private static class RateLimitKey { + private final LimitedApi api; + private final EntityId entityId; } } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java index f69ece6661..4f216ffaca 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java @@ -25,7 +25,8 @@ public enum LimitedApi { ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit), ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit), - NOTIFICATION_REQUEST(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit); + NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit), + NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit); private final Function configExtractor; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java index c984ec8fa5..3fde98618e 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java @@ -15,10 +15,13 @@ */ package org.thingsboard.server.service.apiusage.limits; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; public interface RateLimitService { - boolean checkRateLimit(TenantId tenantId, LimitedApi api); + boolean checkRateLimit(LimitedApi api, TenantId tenantId); + + boolean checkRateLimit(LimitedApi api, TenantId tenantId, EntityId entityId); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index d297f70a7f..91bc0863db 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -107,8 +107,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest request, Consumer callback) { - if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) { - throw new TbRateLimitsException(EntityType.TENANT); + if (request.getRuleId() == null) { + if (!rateLimitService.checkRateLimit(LimitedApi.NOTIFICATION_REQUESTS, tenantId)) { + throw new TbRateLimitsException(EntityType.TENANT); + } } NotificationTemplate notificationTemplate; @@ -119,8 +121,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing"); + Set deliveryMethods = new HashSet<>(); List targets = request.getTargets().stream().map(NotificationTargetId::new) - .map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList()); + .map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)) + .collect(Collectors.toList()); NotificationRuleId ruleId = request.getRuleId(); notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { @@ -128,14 +132,22 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple try { channels.get(deliveryMethod).check(tenantId); } catch (Exception e) { - throw new IllegalArgumentException(e.getMessage()); + if (ruleId == null) { + throw new IllegalArgumentException(e.getMessage()); + } else { + return; // if originated by rule - just ignore delivery method + } } if (ruleId == null) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { throw new IllegalArgumentException("Recipients for " + deliveryMethod.getName() + " delivery method not chosen"); } } + deliveryMethods.add(deliveryMethod); }); + if (deliveryMethods.isEmpty()) { + throw new IllegalArgumentException("No delivery methods to send notification with"); + } if (request.getAdditionalConfig() != null) { NotificationRequestConfig config = request.getAdditionalConfig(); @@ -155,6 +167,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple NotificationProcessingContext ctx = NotificationProcessingContext.builder() .tenantId(tenantId) .request(request) + .deliveryMethods(deliveryMethods) .template(notificationTemplate) .settings(settings) .build(); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java index 8f9b7d048b..25ff1dd50e 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -48,20 +48,21 @@ public class NotificationProcessingContext { private final NotificationSettings settings; @Getter private final NotificationRequest request; - + @Getter + private final Set deliveryMethods; @Getter private final NotificationTemplate notificationTemplate; + private final Map templates; @Getter - private Set deliveryMethods; - @Getter private final NotificationRequestStats stats; - @Builder - public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) { + public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, Set deliveryMethods, + NotificationTemplate template, NotificationSettings settings) { this.tenantId = tenantId; this.request = request; + this.deliveryMethods = deliveryMethods; this.settings = settings; this.notificationTemplate = template; this.templates = new EnumMap<>(NotificationDeliveryMethod.class); @@ -77,7 +78,6 @@ public class NotificationProcessingContext { templates.put(deliveryMethod, template); } }); - deliveryMethods = templates.keySet(); } public C getDeliveryMethodConfig(NotificationDeliveryMethod deliveryMethod) { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java index 2cb4c37210..8b3edbb812 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java @@ -41,9 +41,11 @@ import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigg import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.notification.NotificationRequestService; -import org.thingsboard.server.service.notification.rule.cache.NotificationRulesCache; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.service.apiusage.limits.LimitedApi; +import org.thingsboard.server.service.apiusage.limits.RateLimitService; import org.thingsboard.server.service.executors.NotificationExecutorService; +import org.thingsboard.server.service.notification.rule.cache.NotificationRulesCache; import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor; @@ -65,6 +67,7 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess private final NotificationRulesCache notificationRulesCache; private final NotificationRequestService notificationRequestService; private final PartitionService partitionService; + private final RateLimitService rateLimitService; @Autowired @Lazy private NotificationCenter notificationCenter; private final NotificationExecutorService notificationExecutor; @@ -119,6 +122,11 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess } if (matchesFilter(trigger, triggerConfig)) { + if (!rateLimitService.checkRateLimit(LimitedApi.NOTIFICATION_REQUESTS_PER_RULE, rule.getTenantId(), rule.getId())) { + log.debug("[{}] Rate limit for notification requests per rule was exceeded (rule '{}')", rule.getTenantId(), rule.getName()); + return; + } + NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> { submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index b646cb7ef6..793c25d625 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -73,7 +73,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS @Override public , I extends EntityId> EntityExportData exportEntity(EntitiesExportCtx ctx, I entityId) throws ThingsboardException { - if (!rateLimitService.checkRateLimit(ctx.getTenantId(), LimitedApi.ENTITY_EXPORT)) { + if (!rateLimitService.checkRateLimit(LimitedApi.ENTITY_EXPORT, ctx.getTenantId())) { throw new ThingsboardException("Rate limit for entities export is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); } @@ -85,7 +85,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS @Override public , I extends EntityId> EntityImportResult importEntity(EntitiesImportCtx ctx, EntityExportData exportData) throws ThingsboardException { - if (!rateLimitService.checkRateLimit(ctx.getTenantId(), LimitedApi.ENTITY_IMPORT)) { + if (!rateLimitService.checkRateLimit(LimitedApi.ENTITY_IMPORT, ctx.getTenantId())) { throw new ThingsboardException("Rate limit for entities import is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); } if (exportData.getEntity() == null || exportData.getEntity().getId() == null) { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d51a2f228f..dea1756ae0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -478,10 +478,14 @@ cache: entityCount: timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_COUNT_TTL:1440}" maxSize: "${CACHE_SPECS_ENTITY_COUNT_MAX_SIZE:100000}" + # deliberately placed outside 'specs' group above notificationRules: timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:30}" maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:1000}" + rateLimits: + timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:60}" + maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:100000}" #Disable this because it is not required. spring.data.redis.repositories.enabled: false diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 3e12cb6d05..8fec807d45 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -23,14 +23,12 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.util.Pair; -import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode; -import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; @@ -43,8 +41,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; -import org.thingsboard.server.common.data.id.NotificationRuleId; -import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -59,25 +56,21 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; -import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.notification.NotificationRequestService; -import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.service.apiusage.limits.LimitedApi; +import org.thingsboard.server.service.apiusage.limits.RateLimitService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.util.ArrayList; @@ -96,9 +89,6 @@ import static org.awaitility.Awaitility.await; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest -@TestPropertySource(properties = { - "js.evaluator=local" -}) public class NotificationRuleApiTest extends AbstractNotificationApiTest { @SpyBean @@ -106,18 +96,13 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Autowired private NotificationRequestService notificationRequestService; @Autowired - private NotificationRuleService notificationRuleService; + private TenantProfileService tenantProfileService; @Autowired - private NotificationTemplateService notificationTemplateService; - - @SpyBean - private AlarmService alarmService; + private RateLimitService rateLimitService; @Before public void beforeEach() throws Exception { loginTenantAdmin(); - notificationRuleService.deleteNotificationRulesByTenantId(tenantId); - notificationTemplateService.deleteNotificationTemplatesByTenantId(tenantId); } @Test @@ -208,7 +193,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { String alarmType = "myBoolIsTrue"; - DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType); + DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(alarmType); Device device = createDevice("Device 1", deviceProfile.getName(), "1234"); clients.values().forEach(wsClient -> { @@ -272,7 +257,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM); String alarmType = "myBoolIsTrue"; - DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType); + DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(alarmType); Device device = createDevice("Device 1", deviceProfile.getName(), "1234"); AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig(); @@ -352,7 +337,56 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(ruleInfo.getDeliveryMethods()).containsOnly(deliveryMethods); } - private DeviceProfile createDeviceProfileWithAlarmRules(NotificationRuleId notificationRuleId, String alarmType) { + @Test + public void testNotificationRequestsPerRuleRateLimits() throws Exception { + int notificationRequestsLimit = 10; + TenantProfile tenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID); + TenantProfileData profileData = tenantProfile.getProfileData(); + DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) profileData.getConfiguration(); + profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(notificationRequestsLimit + ":300"); + tenantProfile.setProfileData(profileData); + loginSysAdmin(); + doPost("/api/tenantProfile", tenantProfile).andExpect(status().isOk()); + loginTenantAdmin(); + + NotificationRule rule = new NotificationRule(); + rule.setName("Device created"); + rule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); + NotificationTemplate template = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Device created", "Device created", + NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.SMS); + rule.setTemplateId(template.getId()); + EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); + triggerConfig.setEntityTypes(Set.of(EntityType.DEVICE)); + triggerConfig.setCreated(true); + rule.setTriggerConfig(triggerConfig); + NotificationTarget target = createNotificationTarget(tenantAdminUserId); + DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); + recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); + recipientsConfig.setTargets(List.of(target.getUuidId())); + rule.setRecipientsConfig(recipientsConfig); + rule = saveNotificationRule(rule); + + for (int i = 0; i < notificationRequestsLimit; i++) { + String name = "device " + i; + createDevice(name, name); + } + await().atMost(5, TimeUnit.SECONDS) + .untilAsserted(() -> { + assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit); + }); + for (int i = 0; i < 5; i++) { + String name = "device " + (notificationRequestsLimit + i); + createDevice(name, name); + } + + boolean rateLimitExceeded = !rateLimitService.checkRateLimit(LimitedApi.NOTIFICATION_REQUESTS_PER_RULE, tenantId, rule.getId()); + assertThat(rateLimitExceeded).isTrue(); + + TimeUnit.SECONDS.sleep(3); + assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit); + } + + private DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) { DeviceProfile deviceProfile = createDeviceProfile("For notification rule test"); deviceProfile.setTenantId(tenantId); @@ -387,41 +421,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { return deviceProfile; } - private RuleChain createEmptyRuleChain(String name) { - RuleChain ruleChain = new RuleChain(); - ruleChain.setName(name); - ruleChain.setTenantId(tenantId); - ruleChain.setRoot(false); - ruleChain.setDebugMode(false); - ruleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); - - RuleChainMetaData metaData = new RuleChainMetaData(); - metaData.setRuleChainId(ruleChain.getId()); - metaData.setNodes(List.of()); - metaData = doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class); - return ruleChain; - } - - private RuleNode addRuleNodeWithError(RuleChainId ruleChainId, String name) { - RuleChainMetaData metaData = new RuleChainMetaData(); - metaData.setRuleChainId(ruleChainId); - - RuleNode generatorNodeWithError = new RuleNode(); - generatorNodeWithError.setName(name); - generatorNodeWithError.setType(TbMsgGeneratorNode.class.getName()); - TbMsgGeneratorNodeConfiguration generatorNodeConfiguration = new TbMsgGeneratorNodeConfiguration(); - generatorNodeConfiguration.setScriptLang(ScriptLanguage.JS); - generatorNodeConfiguration.setPeriodInSeconds(1000); - generatorNodeConfiguration.setMsgCount(1); - generatorNodeConfiguration.setJsScript("[return"); - generatorNodeWithError.setConfiguration(mapper.valueToTree(generatorNodeConfiguration)); - - metaData.setNodes(List.of(generatorNodeWithError)); - metaData.setFirstNodeIndex(0); - metaData = doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class); - return metaData.getNodes().get(0); - } - private NotificationRule saveNotificationRule(NotificationRule notificationRule) { return doPost("/api/notification/rule", notificationRule, NotificationRule.class); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java index 1b843d61b4..ce42298d9f 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; import org.thingsboard.server.common.data.id.IdBased; @@ -30,8 +29,6 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.List; @@ -43,16 +40,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class NotificationTemplateApiTest extends AbstractNotificationApiTest { - @Autowired - private NotificationTemplateService templateService; - @Autowired - private NotificationRuleService notificationRuleService; - @Before public void beforeEach() throws Exception { loginTenantAdmin(); - notificationRuleService.deleteNotificationRulesByTenantId(tenantId); - templateService.deleteNotificationTemplatesByTenantId(tenantId); } @Test diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 030587f50f..66fba5f832 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -48,6 +48,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private String tenantEntityExportRateLimit; private String tenantEntityImportRateLimit; private String tenantNotificationRequestsRateLimit; + private String tenantNotificationRequestsPerRuleRateLimit; private long maxTransportMessages; private long maxTransportDataPoints; diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 81a1206800..7c0ce98e4a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -480,6 +480,9 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index e070d760ad..cbb5c7444e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -73,6 +73,7 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA tenantEntityExportRateLimit: [null, []], tenantEntityImportRateLimit: [null, []], tenantNotificationRequestsRateLimit: [null, []], + tenantNotificationRequestsPerRuleRateLimit: [null, []], maxTransportMessages: [null, [Validators.required, Validators.min(0)]], maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], maxREExecutions: [null, [Validators.required, Validators.min(0)]], diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts index 09ae3f5139..26326e9f63 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts @@ -34,7 +34,8 @@ export enum RateLimitsType { CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION = 'CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION', TENANT_ENTITY_EXPORT_RATE_LIMIT = 'TENANT_ENTITY_EXPORT_RATE_LIMIT', TENANT_ENTITY_IMPORT_RATE_LIMIT = 'TENANT_ENTITY_IMPORT_RATE_LIMIT', - TENANT_NOTIFICATION_REQUEST_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUEST_RATE_LIMIT' + TENANT_NOTIFICATION_REQUEST_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUEST_RATE_LIMIT', + TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT' } export const rateLimitsLabelTranslationMap = new Map( @@ -52,6 +53,7 @@ export const rateLimitsLabelTranslationMap = new Map( [RateLimitsType.TENANT_ENTITY_EXPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-export-rate-limit'], [RateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-import-rate-limit'], [RateLimitsType.TENANT_NOTIFICATION_REQUEST_RATE_LIMIT, 'tenant-profile.tenant-notification-request-rate-limit'], + [RateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT, 'tenant-profile.tenant-notification-requests-per-rule-rate-limit'], ] ); @@ -70,6 +72,7 @@ export const rateLimitsDialogTitleTranslationMap = new Map Date: Thu, 13 Apr 2023 18:41:28 +0300 Subject: [PATCH 070/168] Fix getNotificationRequestPreview --- .../thingsboard/server/controller/NotificationController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index d36eb9913d..23aaba0e48 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -251,9 +251,12 @@ public class NotificationController extends BaseController { preview.setRecipientsCountByTarget(recipientsCountByTarget); preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum()); + Set deliveryMethods = template.getConfiguration().getDeliveryMethodsTemplates().entrySet() + .stream().filter(entry -> entry.getValue().isEnabled()).map(Map.Entry::getKey).collect(Collectors.toSet()); NotificationProcessingContext ctx = NotificationProcessingContext.builder() .tenantId(user.getTenantId()) .request(request) + .deliveryMethods(deliveryMethods) .template(template) .settings(null) .build(); From 1fc8d7a52bd5a657fc7f76c059bc5870d7c910ef Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 14 Apr 2023 13:26:28 +0300 Subject: [PATCH 071/168] Add validation for X509 trusted chain --- .../mqtt/MqttSslHandlerProvider.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 1cc4f90915..bfa80ba615 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -141,6 +141,9 @@ public class MqttSslHandlerProvider { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (!validateCertificateChain(chain)) { + throw new CertificateException("Invalid Chain of X509 Certificates. "); + } String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); final String[] credentialsBodyHolder = new String[1]; CountDownLatch latch = new CountDownLatch(1); @@ -176,5 +179,21 @@ public class MqttSslHandlerProvider { log.error(e.getMessage(), e); } } + + private boolean validateCertificateChain(X509Certificate[] chain) { + try { + if (chain.length > 1) { + X509Certificate leafCert = chain[0]; + for (int i = 1; i < chain.length; i++) { + X509Certificate intermediateCert = chain[i]; + leafCert.verify(intermediateCert.getPublicKey()); + leafCert = intermediateCert; + } + } + return true; + } catch (Exception e) { + return false; + } + } } } From f835f74f1c50687ef58f92d9e89cb00edcb3be56 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 14 Apr 2023 16:12:31 +0300 Subject: [PATCH 072/168] UI: Fixed template notification view --- ui-ngx/package.json | 1 - .../components/color-picker/color-picker.component.scss | 4 ++-- ui-ngx/yarn.lock | 7 ------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 557c288049..2c29990466 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -74,7 +74,6 @@ "moment": "^2.29.4", "moment-timezone": "^0.5.42", "ngx-clipboard": "^15.1.0", - "ngx-color-picker": "^14.0.0", "ngx-daterangepicker-material": "^6.0.4", "ngx-drag-drop": "^15.0.1", "ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/2.0.0", diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index 24fd427576..38b55346aa 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -81,7 +81,7 @@ indicator-component { height: 48px; width: 48px; border-radius: 8px; - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==') repeat; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMUAFg9Gm0AAAAPSURBVAjXY2D4jxXhEgYAfr8P8QhChVEAAAAASUVORK5CYII=') repeat; } .hue-alpha-range { @@ -107,7 +107,7 @@ color-presets-component { display: inline-block; height: 20px; width: 20px; - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAYAAAAffCjxAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACewAAAnsB01CO3AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIASURBVEiJ7ZY9axRRFIafsxMStrLQJpAgpBFhi+C9w1YSo00I6RZ/g9vZpBf/QOr4GyRgkSKNSrAadsZqQGwCkuAWyRZJsySwvhZ7N/vhzrgbLH3Ld8597jlzz50zJokyxXH8DqDVar0qi6v8BbItqSGpEcfxdlmsFWXkvX8AfAVWg3UKPEnT9GKujMzsAFgZsVaCN1VTQd77XUnrgE1kv+6935268WRpzrnHZvYRWC7YvC3pRZZl3wozqtVqiyH9IgjAspkd1Gq1xUJQtVrdB9ZKIAOthdg/Qc65LUk7wNIMoCVJO865rYFhkqjX6/d7vV4GPJwBMqofURS5JEk6FYBer/eeYb/Mo9WwFnPOvQbeAvfuAAK4BN4sAJtAG/gJIElmNuiJyba3EGNmZiPeZuEVmVell/Y/6N+CzDn3AXhEOOo7Hv/3BeAz8IzQkMPnJbuPx1wC+yYJ7/0nYIP5S/0FHKdp+rwCEEXRS/rf5Hl1Gtb2M0iSpCOpCZzPATmX1EySpHMLAsiy7MjMDoHrGSDXZnaYZdnRwBh7J91utwmczAA6CbG3GgPleX4jqUH/a1CktqRGnuc3hSCAMB32gKspkCtgb3KCQMmkjeP4WNJThrNNZval1WptTIsv7JtQ4tmIdRa8qSoEpWl6YWZNoAN0zKxZNPehpLSBZv2t+Q0CJ9lLnARQLAAAAABJRU5ErkJggg==') no-repeat center; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAMAAAAootjDAAAAM1BMVEUAAAAzMzMzMzMzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzMyMjIrKyszMzPF8UZlAAAAEHRSTlMA1fHr4ZxxSRP45sG+sCkGH2+Z6QAAAHJJREFUKM+9kkkSgCAQA0FEVLb5/2tViqgQvNrHviSzKGCt6nDGuNass8i8NsrLiX+bZbrUtDwm7VLYE0zWUtEZ+RvUZpEvN8YhH9QmQRoC8kFpEnVHVP/DJUZVeSAem5fDKxwtms/BR+PT8gN8vwk/0wE1gQzNVYryIwAAAABJRU5ErkJggg==') no-repeat center; background-size: 6px 12px; &:hover { diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 7625732209..7764dcbe5d 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -8137,13 +8137,6 @@ ngx-clipboard@^15.1.0: ngx-window-token ">=6.0.0" tslib "^2.0.0" -ngx-color-picker@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/ngx-color-picker/-/ngx-color-picker-14.0.0.tgz#4587f517ac5683a705d4e55cd0939afa91faa853" - integrity sha512-w28zx2DyVpIJeNsTB3T2LUI4Ed/Ujf5Uhxuh0dllputfpxXwZG9ocSJM/0L67+fxA3UnfvvXVZNUX1Ny5nZIIw== - dependencies: - tslib "^2.3.0" - ngx-daterangepicker-material@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/ngx-daterangepicker-material/-/ngx-daterangepicker-material-6.0.4.tgz#4e2432698ded28021a5fc0b0025dd1e0a3a13412" From b09bbff15d293a1e07e08f53e95f9d89180d2d1c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 14 Apr 2023 16:13:10 +0200 Subject: [PATCH 073/168] fixed RestTemplate xml content type issue --- .../system/RestTemplateConvertersTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 application/src/test/java/org/thingsboard/server/system/RestTemplateConvertersTest.java diff --git a/application/src/test/java/org/thingsboard/server/system/RestTemplateConvertersTest.java b/application/src/test/java/org/thingsboard/server/system/RestTemplateConvertersTest.java new file mode 100644 index 0000000000..6a8491304d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/system/RestTemplateConvertersTest.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.system; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.springframework.util.ClassUtils; +import org.springframework.web.client.RestTemplate; + + +@Slf4j +public class RestTemplateConvertersTest { + + @Test + public void testJacksonXmlConverter() { + ClassLoader classLoader = RestTemplate.class.getClassLoader(); + boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); + Assertions.assertFalse(jackson2XmlPresent, "XmlMapper must not be present in classpath, please, exclude \"jackson-dataformat-xml\" dependency!"); + //If this xml mapper will be present in classpath then we will get "Unsupported Media Type" in RestTemplate + } + +} From 08efbd1c85bafb217bf4e825930b0a5077c1ed43 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 14 Apr 2023 18:06:59 +0200 Subject: [PATCH 074/168] revert tskv value noxss validation --- application/src/main/resources/thingsboard.yml | 2 -- .../server/controller/BaseTelemetryControllerTest.java | 6 ++---- .../server/common/data/kv/JsonDataEntry.java | 3 --- .../server/common/data/kv/StringDataEntry.java | 5 +---- .../server/dao/attributes/AttributeUtils.java | 8 ++++---- .../server/dao/attributes/BaseAttributesService.java | 7 ++----- .../server/dao/attributes/CachedAttributesService.java | 7 ++----- .../server/dao/timeseries/BaseTimeseriesService.java | 9 +++------ .../java/org/thingsboard/server/dao/util/KvUtils.java | 10 ++++------ .../server/dao/service/ConstraintValidatorTest.java | 7 ------- 10 files changed, 18 insertions(+), 46 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1c1036dc14..14bf47301b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -259,13 +259,11 @@ sql: batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution - noxss_validation_enabled: "${SQL_ATTRIBUTES_NOXSS_VALIDATION_ENABLED:true}" ts: batch_size: "${SQL_TS_BATCH_SIZE:10000}" batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_TS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution - noxss_validation_enabled: "${SQL_TS_NOXSS_VALIDATION_ENABLED:true}" ts_latest: batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java index af0d486869..50ad731a79 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import org.junit.Test; +import org.stringtemplate.v4.ST; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -32,10 +33,7 @@ public abstract class BaseTelemetryControllerTest extends AbstractControllerTest String correctRequestBody = "{\"data\": \"value\"}"; doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", correctRequestBody, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", correctRequestBody, String.class, status().isOk()); - String invalidRequestBody = "{\"data\": \"alert(document)\\\">\"}"; - doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", invalidRequestBody, String.class, status().isBadRequest()); - doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); - invalidRequestBody = "{\"alert(document)\\\">\": \"data\"}"; + String invalidRequestBody = "{\"alert(document)\\\">\": \"data\"}"; doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", invalidRequestBody, String.class, status().isBadRequest()); doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java index 4260c512f8..0736b50b84 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java @@ -15,14 +15,11 @@ */ package org.thingsboard.server.common.data.kv; -import org.thingsboard.server.common.data.validation.NoXss; - import java.util.Objects; import java.util.Optional; public class JsonDataEntry extends BasicKvEntry { - @NoXss private final String value; public JsonDataEntry(String key, String value) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java index 9251cc80b3..18a54327a2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.common.data.kv; -import org.thingsboard.server.common.data.validation.NoXss; - import java.util.Objects; import java.util.Optional; @@ -24,7 +22,6 @@ public class StringDataEntry extends BasicKvEntry { private static final long serialVersionUID = 1L; - @NoXss private final String value; public StringDataEntry(String key, String value) { @@ -68,7 +65,7 @@ public class StringDataEntry extends BasicKvEntry { public String toString() { return "StringDataEntry{" + "value='" + value + '\'' + "} " + super.toString(); } - + @Override public String getValueAsString() { return value; diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java index 980057a231..d1abeda5b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java @@ -30,12 +30,12 @@ public class AttributeUtils { Validator.validateString(scope, "Incorrect scope " + scope); } - public static void validate(List kvEntries, boolean validateNoxss) { - kvEntries.forEach(kv -> validate(kv, validateNoxss)); + public static void validate(List kvEntries) { + kvEntries.forEach(AttributeUtils::validate); } - public static void validate(AttributeKvEntry kvEntry, boolean validateNoxss) { - KvUtils.validate(kvEntry, validateNoxss); + public static void validate(AttributeKvEntry kvEntry) { + KvUtils.validate(kvEntry); if (kvEntry.getDataType() == null) { throw new IncorrectParameterException("Incorrect kvEntry. Data type can't be null"); } else { diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 08cb8beeae..abd55b6b12 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -46,9 +46,6 @@ import static org.thingsboard.server.dao.attributes.AttributeUtils.validate; public class BaseAttributesService implements AttributesService { private final AttributesDao attributesDao; - @Value("${sql.attributes.noxss_validation_enabled:true}") - private boolean noxssValidationEnabled; - public BaseAttributesService(AttributesDao attributesDao) { this.attributesDao = attributesDao; } @@ -86,14 +83,14 @@ public class BaseAttributesService implements AttributesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); - AttributeUtils.validate(attribute, noxssValidationEnabled); + AttributeUtils.validate(attribute); return attributesDao.save(tenantId, entityId, scope, attribute); } @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); - AttributeUtils.validate(attributes, noxssValidationEnabled); + AttributeUtils.validate(attributes); List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); return Futures.allAsList(saveFutures); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index ef81ed695b..b95ce39d9a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -70,9 +70,6 @@ public class CachedAttributesService implements AttributesService { @Value("${cache.type:caffeine}") private String cacheType; - @Value("${sql.attributes.noxss_validation_enabled:true}") - private boolean noxssValidationEnabled; - public CachedAttributesService(AttributesDao attributesDao, StatsFactory statsFactory, CacheExecutorService cacheExecutorService, @@ -215,7 +212,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); - AttributeUtils.validate(attribute, noxssValidationEnabled); + AttributeUtils.validate(attribute); ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor); } @@ -223,7 +220,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); - AttributeUtils.validate(attributes, noxssValidationEnabled); + AttributeUtils.validate(attributes); List> futures = new ArrayList<>(attributes.size()); for (var attribute : attributes) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 1f5f21acc8..748c57b5dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -81,9 +81,6 @@ public class BaseTimeseriesService implements TimeseriesService { @Value("${database.ts_max_intervals}") private long maxTsIntervals; - @Value("${sql.ts.noxss_validation_enabled:true}") - private boolean noxssValidationEnabled; - @Autowired private TimeseriesDao timeseriesDao; @@ -159,7 +156,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { - KvUtils.validate(tsKvEntry, noxssValidationEnabled); + KvUtils.validate(tsKvEntry); validate(entityId); List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, 0L); @@ -177,7 +174,7 @@ public class BaseTimeseriesService implements TimeseriesService { } private ListenableFuture doSave(TenantId tenantId, EntityId entityId, List tsKvEntries, long ttl, boolean saveLatest) { - KvUtils.validate(tsKvEntries, noxssValidationEnabled); + KvUtils.validate(tsKvEntries); int inserts = saveLatest ? INSERTS_PER_ENTRY : INSERTS_PER_ENTRY_WITHOUT_LATEST; List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * inserts); for (TsKvEntry tsKvEntry : tsKvEntries) { @@ -192,7 +189,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> saveLatest(TenantId tenantId, EntityId entityId, List tsKvEntries) { - KvUtils.validate(tsKvEntries, noxssValidationEnabled); + KvUtils.validate(tsKvEntries); List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size()); for (TsKvEntry tsKvEntry : tsKvEntries) { futures.add(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java index 133fb4c85e..8e80bd992d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java @@ -22,16 +22,14 @@ import org.thingsboard.server.dao.service.ConstraintValidator; import java.util.List; public class KvUtils { - public static void validate(List tsKvEntries, boolean validateNoxss) { - tsKvEntries.forEach(kvEntry -> validate(kvEntry, validateNoxss)); + public static void validate(List tsKvEntries) { + tsKvEntries.forEach(KvUtils::validate); } - public static void validate(KvEntry tsKvEntry, boolean validateNoxss) { + public static void validate(KvEntry tsKvEntry) { if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } - if (validateNoxss) { - ConstraintValidator.validateFields(tsKvEntry); - } + ConstraintValidator.validateFields(tsKvEntry); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java index fa3d39df30..24a32b6a61 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.service; import org.junit.Assert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.dao.exception.DataValidationException; @@ -30,15 +29,9 @@ class ConstraintValidatorTest { @Test void validateFields() { StringDataEntry stringDataEntryValid = new StringDataEntry("key", "value"); - StringDataEntry stringDataEntryInvalid1 = new StringDataEntry("", "value"); - StringDataEntry stringDataEntryInvalid2 = new StringDataEntry("key", ""); - - JsonDataEntry jsonDataEntryInvalid = new JsonDataEntry("key", "{\"value\": }"); Assert.assertThrows(DataValidationException.class, () -> ConstraintValidator.validateFields(stringDataEntryInvalid1)); - Assert.assertThrows(DataValidationException.class, () -> ConstraintValidator.validateFields(stringDataEntryInvalid2)); - Assert.assertThrows(DataValidationException.class, () -> ConstraintValidator.validateFields(jsonDataEntryInvalid)); ConstraintValidator.validateFields(stringDataEntryValid); } From bcc5eba8e6e136eb8fe0ecb0eb14a2afecf8e834 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 14 Apr 2023 19:18:35 +0300 Subject: [PATCH 075/168] UI: Tenant admin home page initial implementation --- .../getting-started-widget.component.html | 112 +++ .../getting-started-widget.component.scss | 117 ++- .../getting-started-widget.component.ts | 4 +- .../lib/home-page/home-page-widgets.module.ts | 7 +- .../home-page/toggle-header.component.html | 20 + .../home-page/toggle-header.component.scss | 87 +++ .../lib/home-page/toggle-header.component.ts | 82 +++ .../home-links/home-links-routing.module.ts | 15 +- .../home-links/tenant_admin_home_page.raw | 667 ++++++++++++++++++ .../shared/components/markdown.component.html | 2 +- .../shared/components/markdown.component.ts | 127 ++-- .../assets/locale/locale.constant-en_US.json | 49 ++ 12 files changed, 1216 insertions(+), 73 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html index b2425a0231..ee84a63ad6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html @@ -91,6 +91,118 @@ description{{ 'widgets.getting-started.sys-admin.step6.how-to-configure-notifications' | translate }} + + + +
    +
    widgets.getting-started.tenant-admin.step1.title
    + {{ 'device.devices' | translate }} +
    +
    +
    + + description{{ 'widgets.getting-started.tenant-admin.step1.how-to-create-device' | translate }} +
    + + +
    +
    widgets.getting-started.tenant-admin.step2.title
    +
    +
    +
    +
    + + Ubuntu + MacOS + Windows + + + +

    + +
    + +

    + +
    + +

    +
    +
    +

    + +
    +
    + + description{{ 'widgets.getting-started.tenant-admin.step2.how-to-connect-device' | translate }} +
    + + +
    +
    widgets.getting-started.tenant-admin.step3.title
    + {{ 'dashboard.dashboards' | translate }} +
    +
    +
    + + description{{ 'widgets.getting-started.tenant-admin.step3.how-to-create-dashboard' | translate }} +
    + + +
    +
    widgets.getting-started.tenant-admin.step4.title
    + {{ 'widgets.getting-started.tenant-admin.step4.alarm-rules' | translate }} +
    +
    +
    + + description{{ 'widgets.getting-started.tenant-admin.step4.how-to-configure-alarm-rules' | translate }} +
    + + +
    +
    widgets.getting-started.tenant-admin.step5.title
    +
    +
    +
    +

    + +
    + + description{{ 'widgets.getting-started.tenant-admin.step5.how-to-create-alarm' | translate }} +
    + + +
    +
    widgets.getting-started.tenant-admin.step6.title
    + {{ 'customer.customers' | translate }} +
    +
    +
    + + description{{ 'widgets.getting-started.tenant-admin.step6.how-to-create-customer-and-assign-dashboard' | translate }} +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss index 193fb5e793..20345a97f4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss @@ -83,13 +83,105 @@ color: rgba(0, 0, 0, 0.87); } - .tb-get-started .mat-vertical-content p { - font-style: normal; - font-weight: 400; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.2px; - color: rgba(0, 0, 0, 0.54); + .tb-get-started .mat-vertical-content { + p, li { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.54); + em { + font-style: normal; + font-weight: 500; + font-size: 13px; + color: rgba(0, 0, 0, 0.38); + } + } + ul { + padding-inline-start: 20px; + } + .tb-bordered-content { + display: flex; + flex-direction: column; + align-items: stretch; + border: 1px solid rgba(0, 0, 0, 0.05); + border-radius: 8px; + padding: 16px; + p { + margin-top: 16px; + margin-bottom: 8px; + } + } + .tb-markdown-view { + .tb-getting-started-code { + .code-wrapper { + padding: 0; + pre[class*=language-] { + margin: 0; + padding: 9px 38px 9px 16px; + background: rgba(0, 0, 0, 0.03); + border-radius: 6px; + border: none; + } + code[class*="language-"], pre[class*="language-"] { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.38); + overflow: hidden; + white-space: break-spaces; + word-break: break-all; + & * { + color: rgba(0, 0, 0, 0.38); + cursor: inherit; + background: transparent; + } + } + button.clipboard-btn { + right: 0; + height: 34px; + p, div { + background: transparent; + } + p { + margin: 0; + padding: 7px; + color: #305680; + } + div { + top: 0; + padding: 8px; + height: 34px; + width: 34px; + img { + display: none; + } + &:after { + content: ""; + position: initial; + display: block; + width: 18px; + height: 18px; + background: #305680; + -webkit-mask-image: url(/assets/copy-code-icon.svg); + -webkit-mask-repeat: no-repeat; + mask-image: url(/assets/copy-code-icon.svg); + mask-repeat: no-repeat; + } + } + } + } + } + } + @media #{$mat-md-lg} { + .tb-bordered-content { + padding: 4px; + } + } } @media #{$mat-md-lg} { @@ -107,15 +199,16 @@ line-height: 16px; } - .tb-get-started .mat-vertical-content p { - font-size: 12px; - line-height: 16px; - letter-spacing: 0.25px; + .tb-get-started .mat-vertical-content { + p, li { + font-size: 12px; + line-height: 16px; + letter-spacing: 0.25px; + } } .tb-get-started .mat-vertical-content { padding: 0 16px 16px 16px; } - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts index 9c07e62978..7566ae8579 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts @@ -27,7 +27,7 @@ import { } from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component'; import { GettingStarted } from '@shared/models/user-settings.models'; import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper'; -import { isUndefined } from '@core/utils'; +import { baseUrl, isUndefined } from '@core/utils'; import { MatStepper } from '@angular/material/stepper'; import { first } from 'rxjs/operators'; import { Authority } from '@shared/models/authority.enum'; @@ -54,6 +54,8 @@ export class GettingStartedWidgetComponent extends PageComponent implements OnIn }; allCompleted = false; + baseUrl = baseUrl(); + constructor(protected store: Store, private cd: ChangeDetectorRef, private userSettingsService: UserSettingsService, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts index 3be30dad30..debd1d6ca0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts @@ -28,6 +28,7 @@ import { GettingStartedWidgetComponent } from '@home/components/widget/lib/home- import { GettingStartedCompletedDialogComponent } from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component'; +import { ToggleHeaderComponent } from '@home/components/widget/lib/home-page/toggle-header.component'; @NgModule({ declarations: @@ -40,7 +41,8 @@ import { AddDocLinkDialogComponent, EditDocLinksDialogComponent, GettingStartedWidgetComponent, - GettingStartedCompletedDialogComponent + GettingStartedCompletedDialogComponent, + ToggleHeaderComponent ], imports: [ CommonModule, @@ -55,7 +57,8 @@ import { AddDocLinkDialogComponent, EditDocLinksDialogComponent, GettingStartedWidgetComponent, - GettingStartedCompletedDialogComponent + GettingStartedCompletedDialogComponent, + ToggleHeaderComponent ] }) export class HomePageWidgetsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html new file mode 100644 index 0000000000..3cbf61fdae --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html @@ -0,0 +1,20 @@ + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss new file mode 100644 index 0000000000..0dce288424 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import "../../../../../../../scss/constants"; + +:host ::ng-deep { + .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { + width: 100%; + border-radius: 100px; + height: 32px; + padding: 2px; + border: none; + background: rgba(0, 0, 0, 0.06); + margin-bottom: 8px; + .mat-button-toggle + .mat-button-toggle { + border-left: none; + } + .mat-button-toggle.mat-button-toggle-appearance-standard { + flex: 1; + color: rgba(0, 0, 0, 0.38); + background: transparent; + + .mat-button-toggle-focus-overlay, .mat-button-toggle-ripple { + border-radius: 20px; + } + .mat-button-toggle-button { + height: 28px; + .mat-button-toggle-label-content { + line-height: 26px; + font-weight: 400; + font-size: 14px; + letter-spacing: 0.2px; + } + } + &.mat-button-toggle-checked { + .mat-button-toggle-button { + background: #F3F6FA; + color: #305680; + border: 1px solid #305680; + border-radius: 20px; + + .mat-button-toggle-label-content { + font-weight: 500; + line-height: 24px; + } + } + } + } + } + @media #{$mat-md-lg} { + .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { + height: 24px; + margin-bottom: 0; + .mat-button-toggle.mat-button-toggle-appearance-standard { + .mat-button-toggle-button { + height: 20px; + display: grid; + .mat-button-toggle-label-content { + line-height: 20px; + font-size: 10px; + padding: 0 2px; + } + } + &.mat-button-toggle-checked { + .mat-button-toggle-button { + .mat-button-toggle-label-content { + line-height: 18px; + } + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts new file mode 100644 index 0000000000..1d7876eb36 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts @@ -0,0 +1,82 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterContentInit, + AfterViewInit, + ChangeDetectorRef, + Component, + ContentChildren, + Input, + OnInit, + QueryList, + ViewChild +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { UpdateMessage } from '@shared/models/settings.models'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; +import { of } from 'rxjs'; +import { MatStepper } from '@angular/material/stepper'; +import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle'; + +@Component({ + selector: 'tb-toggle-header', + templateUrl: './toggle-header.component.html', + styleUrls: ['./toggle-header.component.scss'] +}) +export class ToggleHeaderComponent extends PageComponent implements OnInit, AfterViewInit { + + @ViewChild('toggleGroup') + toggleGroup: MatButtonToggleGroup; + + @ContentChildren(MatButtonToggle) + _buttonToggles: QueryList; + + innerValue: any; + + @Input() + set value(value: any) { + this.innerValue = value; + } + + get value(): any { + return this.toggleGroup?.value; + } + + @Input() + name: string; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + } + + ngAfterViewInit() { + for (const toggle of this._buttonToggles) { + toggle.buttonToggleGroup = this.toggleGroup; + if (this.innerValue === toggle.value) { + toggle.checked = true; + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts index be408da234..3e0f797be1 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts @@ -27,6 +27,7 @@ import { AppState } from '@core/core.state'; import { map } from 'rxjs/operators'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import sysAdminHomePageDashboardJson from '!raw-loader!./sys_admin_home_page.raw'; +import tenantAdminHomePageDashboardJson from '!raw-loader!./tenant_admin_home_page.raw'; @Injectable() export class HomeDashboardResolver implements Resolve { @@ -39,8 +40,18 @@ export class HomeDashboardResolver implements Resolve { return this.dashboardService.getHomeDashboard().pipe( map((dashboard) => { if (!dashboard) { - if (getCurrentAuthUser(this.store).authority === Authority.SYS_ADMIN) { - dashboard = JSON.parse(sysAdminHomePageDashboardJson); + const authority = getCurrentAuthUser(this.store).authority; + switch (authority) { + case Authority.SYS_ADMIN: + dashboard = JSON.parse(sysAdminHomePageDashboardJson); + break; + case Authority.TENANT_ADMIN: + dashboard = JSON.parse(tenantAdminHomePageDashboardJson); + break; + case Authority.CUSTOMER_USER: + break; + } + if (dashboard) { dashboard.hideDashboardToolbar = true; } } diff --git a/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw b/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw new file mode 100644 index 0000000000..3044499da0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw @@ -0,0 +1,667 @@ +{ + "title": "Tenant Administrator Home Page", + "image": null, + "mobileHide": false, + "mobileOrder": null, + "configuration": { + "description": "", + "widgets": { + "d70cc256-4c7b-ee06-9905-b8c5e546605f": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "markdown_card", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1680168340431, + "endTimeMs": 1680254740431 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "16px", + "settings": { + "useMarkdownTextFunction": false, + "markdownTextPattern": "
    \n
    \n
    {{ 'widgets.activity.title' | translate }}
    \n \n {{ 'device.devices' | translate }}\n {{ 'widgets.transport-messages.title' | translate }}\n \n
    \n \n \n \n \n \n \n \n \n
    ", + "applyDefaultMarkdownStyle": false, + "markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n}\n" + }, + "title": "Transport messages", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": false, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "" + }, + "row": 0, + "col": 0, + "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f" + }, + "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:widgets.transport-messages.title}", + "color": "#305680", + "settings": {}, + "_hash": 0.2880464219129071, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "latestDataKeys": [] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": true, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1680443065451, + "endTimeMs": 1680529465451 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "SUM", + "limit": 50000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "stack": true, + "fontSize": 10, + "fontColor": "#545454", + "showTooltip": true, + "tooltipIndividual": false, + "tooltipCumulative": false, + "hideZeros": false, + "grid": { + "verticalLines": false, + "horizontalLines": false, + "outlineWidth": 0, + "color": "#545454", + "backgroundColor": null, + "tickColor": "#DDDDDD" + }, + "xaxis": { + "title": null, + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "min": 0, + "max": null, + "title": null, + "showLabels": true, + "color": "#545454", + "tickSize": null, + "tickDecimals": 0, + "ticksFormatter": "return value % 1000 === 0 ? ((value / 1000) + 'k') : '';" + }, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "comparisonEnabled": false, + "timeForComparison": "previousInterval", + "comparisonCustomIntervalValue": 7200000, + "xaxisSecond": { + "axisPosition": "top", + "title": null, + "showLabels": true + }, + "customLegendEnabled": false, + "dataKeysListForLabels": [] + }, + "title": "Transport messages", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": { + "padding": "0" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "titleTooltip": "", + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-widget-container > .tb-widget .flot-base {\n opacity: 0.48;\n}\n", + "pageSize": 1024, + "noDataDisplayMessage": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + } + }, + "row": 0, + "col": 0, + "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e" + }, + "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": { + "isSystemType": true, + "bundleAlias": "home_page_widgets", + "typeAlias": "documentation_links", + "type": "static", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "16px", + "settings": { + "columns": 2 + }, + "title": "Documentation", + "dropShadow": false, + "enableFullscreen": false, + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "showLegend": false + }, + "row": 0, + "col": 0, + "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4" + }, + "a23185ad-dc46-806c-0e50-5b21fb080ace": { + "isSystemType": true, + "bundleAlias": "home_page_widgets", + "typeAlias": "getting_started", + "type": "static", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "datasources": [], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "16px", + "settings": { + "columns": 3 + }, + "title": "Getting started", + "dropShadow": false, + "enableFullscreen": false, + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "showLegend": false + }, + "row": 0, + "col": 0, + "id": "a23185ad-dc46-806c-0e50-5b21fb080ace" + }, + "d26e5cd7-75ef-d475-00c7-1a2d1114efe8": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "basic_timeseries", + "type": "timeseries", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "filterId": null, + "dataKeys": [ + { + "name": "activeDevicesCount", + "type": "timeseries", + "label": "{i18n:device.devices}", + "color": "#305680", + "settings": { + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "excludeFromStacking": false, + "showLines": true, + "lineWidth": 3, + "fillLines": true, + "showPoints": false, + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showPointShape": "circle", + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", + "showSeparateAxis": false, + "axisPosition": "left", + "comparisonSettings": { + "showValuesForComparison": true, + "comparisonValuesLabel": "", + "color": "" + }, + "thresholds": [] + }, + "_hash": 0.9688095820365725, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "latestDataKeys": [] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": true, + "hideAggInterval": true, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 7200000, + "fixedTimewindow": { + "startTimeMs": 1681400576338, + "endTimeMs": 1681486976338 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "NONE", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "stack": false, + "fontSize": 10, + "fontColor": "#545454", + "showTooltip": true, + "tooltipIndividual": false, + "tooltipCumulative": false, + "hideZeros": false, + "grid": { + "verticalLines": false, + "horizontalLines": false, + "outlineWidth": 0, + "color": "#545454", + "backgroundColor": null, + "tickColor": "#DDDDDD" + }, + "xaxis": { + "title": null, + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "min": null, + "max": null, + "title": null, + "showLabels": true, + "color": "#545454", + "tickSize": null, + "tickDecimals": 0, + "ticksFormatter": "" + }, + "shadowSize": 0, + "smoothLines": true, + "comparisonEnabled": false, + "timeForComparison": "previousInterval", + "comparisonCustomIntervalValue": 7200000, + "xaxisSecond": { + "axisPosition": "top", + "title": null, + "showLabels": true + }, + "customLegendEnabled": false, + "dataKeysListForLabels": [] + }, + "title": "Devices activity", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": false, + "displayTimewindow": true, + "showTitleIcon": false, + "titleTooltip": "", + "widgetStyle": { + "padding": "0" + }, + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}", + "pageSize": 1024, + "noDataDisplayMessage": "", + "showLegend": false, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + } + }, + "row": 0, + "col": 0, + "id": "d26e5cd7-75ef-d475-00c7-1a2d1114efe8" + } + }, + "states": { + "default": { + "name": "Tenant Administrator Home Page", + "root": true, + "layouts": { + "main": { + "widgets": { + "d70cc256-4c7b-ee06-9905-b8c5e546605f": { + "sizeX": 41, + "sizeY": 16, + "row": 26, + "col": 44, + "mobileOrder": 7, + "mobileHeight": 8 + }, + "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": { + "sizeX": 31, + "sizeY": 16, + "row": 42, + "col": 26, + "mobileHide": true + }, + "a23185ad-dc46-806c-0e50-5b21fb080ace": { + "sizeX": 35, + "sizeY": 58, + "row": 0, + "col": 85, + "mobileHide": true + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "columns": 120, + "margin": 12, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 20, + "outerMargin": true + } + } + } + }, + "transport_messages": { + "name": "Transport messages", + "root": false, + "layouts": { + "main": { + "widgets": { + "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { + "sizeX": 24, + "sizeY": 11, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#ffffff", + "columns": 24, + "margin": 0, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": true, + "mobileRowHeight": 70, + "outerMargin": true + } + } + } + }, + "devices_activity": { + "name": "Devices activity", + "root": false, + "layouts": { + "main": { + "widgets": { + "d26e5cd7-75ef-d475-00c7-1a2d1114efe8": { + "sizeX": 24, + "sizeY": 11, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#ffffff", + "columns": 24, + "margin": 0, + "outerMargin": true, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": true, + "mobileRowHeight": 70 + } + } + } + } + }, + "entityAliases": { + "ae870700-071f-b3bc-406c-16ba554c5a55": { + "id": "ae870700-071f-b3bc-406c-16ba554c5a55", + "alias": "Tenants", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "TENANT" + } + }, + "ca4d90e4-f9ae-6fca-6b09-85815a48d52b": { + "id": "ca4d90e4-f9ae-6fca-6b09-85815a48d52b", + "alias": "TenantProfiles", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "TENANT" + } + }, + "a1ddb8fa-90ff-5598-e7f2-e254194d055d": { + "id": "a1ddb8fa-90ff-5598-e7f2-e254194d055d", + "alias": "Devices", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "DEVICE" + } + }, + "619cdf00-a042-3b55-124e-194c1b28c236": { + "id": "619cdf00-a042-3b55-124e-194c1b28c236", + "alias": "Assets", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "ASSET" + } + }, + "1d97ff7f-8b42-5882-f87b-16f3d0dee4f2": { + "id": "1d97ff7f-8b42-5882-f87b-16f3d0dee4f2", + "alias": "Users", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "USER" + } + }, + "0dd2b154-59f4-0f97-da1a-d85f5b5cfe31": { + "id": "0dd2b154-59f4-0f97-da1a-d85f5b5cfe31", + "alias": "Customers", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "CUSTOMER" + } + }, + "d9229b29-3f46-de8d-7fe8-eb0c43c75079": { + "id": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "alias": "Api Usage State", + "filter": { + "type": "apiUsageState", + "resolveMultiple": true + } + } + }, + "filters": {}, + "timewindow": { + "displayValue": "", + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1680168326072, + "endTimeMs": 1680254726072 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "settings": { + "stateControllerId": "entity", + "showTitle": false, + "showDashboardsSelect": true, + "showEntitiesSelect": true, + "showDashboardTimewindow": true, + "showDashboardExport": true, + "toolbarAlwaysOpen": true, + "titleColor": "rgba(0,0,0,0.870588)", + "showDashboardLogo": false, + "dashboardLogoUrl": null, + "hideToolbar": true, + "showFilters": true, + "showUpdateDashboardImage": true, + "dashboardCss": ".tb-widget-container > .tb-widget {\n border: 1px solid rgba(0, 0, 0, 0.05);\n box-shadow: 0px 5px 16px rgba(0, 0, 0, 0.04);\n border-radius: 12px;\n}\n\n.tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 16px !important;\n}\n\n.tb-card-title {\n display: grid;\n}\n\n.tb-home-widget-title {\n font-style: normal;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tb-home-widget-link {\n position: relative;\n border-bottom: none;\n}\n\n.tb-home-widget-link:hover {\n border-bottom: none;\n}\n\n.tb-home-widget-link:focus {\n border-bottom: none;\n}\n\n.tb-home-widget-link::after {\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px; \n}\n\n.tb-home-widget-link:hover::after {\n color: inherit;\n}\n\n.tb-home-widget-info-icon {\n color: rgba(0, 0, 0, 0.12);\n font-size: 16px;\n width: 16px;\n height: 16px;\n line-height: 15px;\n vertical-align: middle;\n}\n\n.tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n padding: 0;\n}\n\n.tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n cursor: pointer;\n user-select: none;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n color: rgba(0, 0, 0, 0.54);\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .tb-widget-container > .tb-widget {\n border-radius: 4px;\n }\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 2px !important;\n }\n .tb-hide-md {\n display: none;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 8px !important;\n }\n .tb-hide-lg {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-hide-md-lg {\n display: none;\n }\n\n .tb-home-widget-title {\n font-size: 12px;\n line-height: 16px;\n }\n \n .tb-widget-container > .tb-widget .tb-widget-title {\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 12px;\n line-height: 16px;\n min-height: 24px;\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 {\n width: 24px;\n height: 24px;\n line-height: 24px;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 .mat-icon {\n width: 18px;\n height: 18px;\n font-size: 18px;\n }\n \n .tb-widget-container > .tb-widget tb-legend {\n padding-bottom: 0 !important;\n }\n \n .tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n font-size: 11px;\n line-height: 16px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (max-width: 959px), screen and (min-width: 1820px) {\n .tb-hide-not-md-lg {\n display: none;\n }\n}\n" + } + }, + "externalId": null, + "name": "Tenant Administrator Home Page" +} \ No newline at end of file diff --git a/ui-ngx/src/app/shared/components/markdown.component.html b/ui-ngx/src/app/shared/components/markdown.component.html index 5b8b9f8af0..67e8cd0da2 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.html +++ b/ui-ngx/src/app/shared/components/markdown.component.html @@ -22,5 +22,5 @@ background: #efefef;"> {{error}}
    -
    +
    diff --git a/ui-ngx/src/app/shared/components/markdown.component.ts b/ui-ngx/src/app/shared/components/markdown.component.ts index 5af7ccce6b..753effc24f 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.ts +++ b/ui-ngx/src/app/shared/components/markdown.component.ts @@ -39,6 +39,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { SHARED_MODULE_TOKEN } from '@shared/components/tokens'; import { deepClone, guid, isDefinedAndNotNull } from '@core/utils'; import { Observable, of, ReplaySubject } from 'rxjs'; +import { coerceBoolean } from '@shared/decorators/coerce-boolean'; let defaultMarkdownStyle; @@ -76,6 +77,10 @@ export class TbMarkdownComponent implements OnChanges { get fallbackToPlainMarkdown(): boolean { return this.fallbackToPlainMarkdownValue; } set fallbackToPlainMarkdown(value: boolean) { this.fallbackToPlainMarkdownValue = coerceBooleanProperty(value); } + @Input() + @coerceBoolean() + usePlainMarkdown = false; + @Output() ready = new EventEmitter(); private lineNumbersValue = false; @@ -124,13 +129,8 @@ export class TbMarkdownComponent implements OnChanges { } template = this.sanitizeCurlyBraces(template); this.markdownContainer.clear(); - const parent = this; - let readyObservable: Observable; - let compileModules = [this.sharedModule]; - if (this.additionalCompileModules) { - compileModules = compileModules.concat(this.additionalCompileModules); - } let styles: string[] = []; + let readyObservable: Observable; if (this.applyDefaultMarkdownStyle) { if (!defaultMarkdownStyle) { defaultMarkdownStyle = deepClone(TbMarkdownComponent['ɵcmp'].styles)[0].replace(/\[_nghost\-%COMP%\]/g, '') @@ -141,70 +141,87 @@ export class TbMarkdownComponent implements OnChanges { if (this.additionalStyles) { styles = styles.concat(this.additionalStyles); } - this.dynamicComponentFactoryService.createDynamicComponentFactory( - class TbMarkdownInstance { - ngOnDestroy(): void { - parent.destroyMarkdownInstanceResources(); - } - }, - template, - compileModules, - true, 1, styles - ).subscribe((factory) => { - this.tbMarkdownInstanceComponentFactory = factory; - const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector}); - try { - this.tbMarkdownInstanceComponentRef = - this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentFactory, 0, injector); - if (this.context) { - for (const propName of Object.keys(this.context)) { - this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName]; - } - } - this.tbMarkdownInstanceComponentRef.instance.style = this.style; - readyObservable = this.handleImages(this.tbMarkdownInstanceComponentRef.location.nativeElement); - this.cd.detectChanges(); - this.error = null; - } catch (error) { - readyObservable = this.handleError(template, error, styles); - } - readyObservable.subscribe(() => { - this.ready.emit(); - }); - }, - (error) => { - readyObservable = this.handleError(template, error, styles); + if (this.usePlainMarkdown) { + readyObservable = this.plainMarkdown(template, styles); this.cd.detectChanges(); readyObservable.subscribe(() => { this.ready.emit(); }); - }); + } else { + const parent = this; + let compileModules = [this.sharedModule]; + if (this.additionalCompileModules) { + compileModules = compileModules.concat(this.additionalCompileModules); + } + this.dynamicComponentFactoryService.createDynamicComponentFactory( + class TbMarkdownInstance { + ngOnDestroy(): void { + parent.destroyMarkdownInstanceResources(); + } + }, + template, + compileModules, + true, 1, styles + ).subscribe((factory) => { + this.tbMarkdownInstanceComponentFactory = factory; + const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector}); + try { + this.tbMarkdownInstanceComponentRef = + this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentFactory, 0, injector); + if (this.context) { + for (const propName of Object.keys(this.context)) { + this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName]; + } + } + this.tbMarkdownInstanceComponentRef.instance.style = this.style; + readyObservable = this.handleImages(this.tbMarkdownInstanceComponentRef.location.nativeElement); + this.cd.detectChanges(); + this.error = null; + } catch (error) { + readyObservable = this.handleError(template, error, styles); + } + readyObservable.subscribe(() => { + this.ready.emit(); + }); + }, + (error) => { + readyObservable = this.handleError(template, error, styles); + this.cd.detectChanges(); + readyObservable.subscribe(() => { + this.ready.emit(); + }); + }); + } } private handleError(template: string, error, styles?: string[]): Observable { this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '
    '); this.markdownContainer.clear(); if (this.fallbackToPlainMarkdownValue) { - const element = this.fallbackElement.nativeElement; - let styleElement; - if (styles?.length) { - const markdownClass = 'tb-markdown-view-' + guid(); - let innerStyle = styles.join('\n'); - innerStyle = innerStyle.replace(/\.tb-markdown-view/g, '.' + markdownClass); - template = template.replace(/tb-markdown-view/g, markdownClass); - styleElement = this.renderer.createElement('style'); - styleElement.innerHTML = innerStyle; - } - element.innerHTML = template; - if (styleElement) { - this.renderer.appendChild(element, styleElement); - } - return this.handleImages(element); + return this.plainMarkdown(template, styles); } else { return of(null); } } + private plainMarkdown(template: string, styles?: string[]): Observable { + const element = this.fallbackElement.nativeElement; + let styleElement; + if (styles?.length) { + const markdownClass = 'tb-markdown-view-' + guid(); + let innerStyle = styles.join('\n'); + innerStyle = innerStyle.replace(/\.tb-markdown-view/g, '.' + markdownClass); + template = template.replace(/tb-markdown-view/g, markdownClass); + styleElement = this.renderer.createElement('style'); + styleElement.innerHTML = innerStyle; + } + element.innerHTML = template; + if (styleElement) { + this.renderer.appendChild(element, styleElement); + } + return this.handleImages(element); + } + private handlePlugins(element: HTMLElement): void { if (this.lineNumbers) { this.setPluginClass(element, PrismPlugin.LineNumbers); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 610cdb209e..0c318d4849 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5114,6 +5114,9 @@ "title": "Transport messages", "info": "All the messages that came from devices" }, + "activity": { + "title": "Activity" + }, "documentation": { "title": "Documentation", "add-link": "Add link", @@ -5201,6 +5204,52 @@ "content": "

    Some text

    Follow the documentation on how to do it:

    ", "how-to-configure-notifications": "How to configure Notifications" } + }, + "tenant-admin": { + "step1": { + "title": "Create device", + "content": "

    We will manually provision the device using the UI. Follow the documentation on how to do it:

    ", + "how-to-create-device": "How to create Device" + }, + "step2": { + "title": "Connect device", + "content-before": "

    To connect the device you need to get the device credentials. But we recommend using the default auto-generated one.

    • Go to device table
    • Click on the device row to open device details
    • Press the button \"Copy access token\"

    Use simple commands to publish data over HTTP:

    ", + "ubuntu": { + "install-curl": "Install cURL for Ubuntu:" + }, + "macos": { + "install-curl": "Install cURL for MacOS:" + }, + "windows": { + "install-curl": "Starting Windows 10 b17063, cURL is available by default." + }, + "replace-access-token": "Replace $ACCESS_TOKEN with your device's token:", + "content-after": "

    You can also use other protocols such as MQTT, CoAP, etc.

    Follow the documentation on how to do it:

    ", + "how-to-connect-device": "How to connect Device" + }, + "step3": { + "title": "Create dashboard", + "content": "

    Create a dashboard to visualize data from entities such as assets, devices, etc.

    Follow the documentation on how to do it:

    ", + "how-to-create-dashboard": "How to create Dashboard" + }, + "step4": { + "title": "Configure alarm rules", + "alarm-rules": "Alarm rules", + "content": "

    When the temperature reaches 25°C, we will raise an alarm. Follow the documentation on how to do it:

    ", + "how-to-configure-alarm-rules": "How to configure Alarm rules" + }, + "step5": { + "title": "Create alarm", + "content-before": "

    To trigger the alarm, send a new telemetry value of 26°C or higher.

    ", + "replace-access-token": "Replace $ACCESS_TOKEN with your device's token:", + "content-after": "

    Follow the documentation on how to do it:

    ", + "how-to-create-alarm": "How to create Alarm" + }, + "step6": { + "title": "Create customer and assign dashboard", + "content": "

    By creating end-user dashboards, a customer user can only see his own devices, and data from another customer will be hidden.

    Follow the documentation on how to do it:

    ", + "how-to-create-customer-and-assign-dashboard": "How to create Customer and assign Dashboard" + } } } }, From 6bb1fd4f12eeebd63ffcccfbee6e4a977c88b6a4 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 17 Apr 2023 12:02:26 +0300 Subject: [PATCH 076/168] PROD-1948: bugfixes for delay and deduplication nodes --- .../deduplication/TbMsgDeduplicationNode.java | 12 +- .../rule/engine/delay/TbMsgDelayNode.java | 12 +- .../action/TbMsgDeduplicationNodeTest.java | 402 ------------------ .../transform/TbMsgDeduplicationNodeTest.java | 41 +- 4 files changed, 58 insertions(+), 409 deletions(-) delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbMsgDeduplicationNodeTest.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index e8816ff607..beae54dce6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -43,7 +43,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @RuleNode( - type = ComponentType.ACTION, + type = ComponentType.TRANSFORMATION, name = "deduplication", configClazz = TbMsgDeduplicationNodeConfiguration.class, nodeDescription = "Deduplicate messages for a configurable period based on a specified deduplication strategy.", @@ -153,7 +153,15 @@ public class TbMsgDeduplicationNode implements TbNode { } } } - deduplicationResults.add(resultMsg); + if (resultMsg != null) { + deduplicationResults.add(TbMsg.newMsg( + resultMsg.getQueueName(), + resultMsg.getType(), + resultMsg.getOriginator(), + resultMsg.getCustomerId(), + resultMsg.getMetaData(), + resultMsg.getData())); + } } packBoundsOpt = findValidPack(msgList, deduplicationTimeoutMs); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 857a068e8c..1f18aea86e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -66,7 +66,17 @@ public class TbMsgDelayNode implements TbNode { if (msg.getType().equals(TB_MSG_DELAY_NODE_MSG)) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { - ctx.enqueueForTellNext(pendingMsg, SUCCESS); + ctx.enqueueForTellNext( + TbMsg.newMsg( + pendingMsg.getQueueName(), + pendingMsg.getType(), + pendingMsg.getOriginator(), + pendingMsg.getCustomerId(), + pendingMsg.getMetaData(), + pendingMsg.getData() + ), + SUCCESS + ); } } else { if (pendingMsgs.size() < config.getMaxPendingMsgs()) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbMsgDeduplicationNodeTest.java deleted file mode 100644 index 1091a7d29c..0000000000 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbMsgDeduplicationNodeTest.java +++ /dev/null @@ -1,402 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.rule.engine.action; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.stubbing.Answer; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; -import org.thingsboard.rule.engine.deduplication.DeduplicationStrategy; -import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNode; -import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNodeConfiguration; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@Slf4j -public class TbMsgDeduplicationNodeTest { - - private static final String MAIN_QUEUE_NAME = "Main"; - private static final String HIGH_PRIORITY_QUEUE_NAME = "HighPriority"; - private static final String TB_MSG_DEDUPLICATION_TIMEOUT_MSG = "TbMsgDeduplicationNodeMsg"; - - private TbContext ctx; - - private final ThingsBoardThreadFactory factory = ThingsBoardThreadFactory.forName("de-duplication-node-test"); - private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(factory); - private final int deduplicationInterval = 1; - - private TenantId tenantId; - - private TbMsgDeduplicationNode node; - private TbMsgDeduplicationNodeConfiguration config; - private TbNodeConfiguration nodeConfiguration; - - private CountDownLatch awaitTellSelfLatch; - - @BeforeEach - public void init() throws TbNodeException { - ctx = mock(TbContext.class); - - tenantId = TenantId.fromUUID(UUID.randomUUID()); - RuleNodeId ruleNodeId = new RuleNodeId(UUID.randomUUID()); - - when(ctx.getSelfId()).thenReturn(ruleNodeId); - when(ctx.getTenantId()).thenReturn(tenantId); - - doAnswer((Answer) invocationOnMock -> { - String type = (String) (invocationOnMock.getArguments())[1]; - EntityId originator = (EntityId) (invocationOnMock.getArguments())[2]; - TbMsgMetaData metaData = (TbMsgMetaData) (invocationOnMock.getArguments())[3]; - String data = (String) (invocationOnMock.getArguments())[4]; - return TbMsg.newMsg(type, originator, metaData.copy(), data); - }).when(ctx).newMsg(isNull(), eq(TB_MSG_DEDUPLICATION_TIMEOUT_MSG), nullable(EntityId.class), any(TbMsgMetaData.class), any(String.class)); - node = spy(new TbMsgDeduplicationNode()); - config = new TbMsgDeduplicationNodeConfiguration().defaultConfiguration(); - } - - private void invokeTellSelf(int maxNumberOfInvocation) { - invokeTellSelf(maxNumberOfInvocation, false, 0); - } - - private void invokeTellSelf(int maxNumberOfInvocation, boolean delayScheduleTimeout, int delayMultiplier) { - AtomicLong scheduleTimeout = new AtomicLong(deduplicationInterval); - AtomicInteger scheduleCount = new AtomicInteger(0); - doAnswer((Answer) invocationOnMock -> { - scheduleCount.getAndIncrement(); - if (scheduleCount.get() <= maxNumberOfInvocation) { - TbMsg msg = (TbMsg) (invocationOnMock.getArguments())[0]; - executorService.schedule(() -> { - try { - node.onMsg(ctx, msg); - awaitTellSelfLatch.countDown(); - } catch (ExecutionException | InterruptedException | TbNodeException e) { - log.error("Failed to execute tellSelf method call due to: ", e); - } - }, scheduleTimeout.get(), TimeUnit.SECONDS); - if (delayScheduleTimeout) { - scheduleTimeout.set(scheduleTimeout.get() * delayMultiplier); - } - } - - return null; - }).when(ctx).tellSelf(ArgumentMatchers.any(TbMsg.class), ArgumentMatchers.anyLong()); - } - - @AfterEach - public void destroy() { - executorService.shutdown(); - node.destroy(); - } - - @Test - public void given_100_messages_strategy_first_then_verifyOutput() throws TbNodeException, ExecutionException, InterruptedException { - int wantedNumberOfTellSelfInvocation = 2; - int msgCount = 100; - awaitTellSelfLatch = new CountDownLatch(wantedNumberOfTellSelfInvocation); - invokeTellSelf(wantedNumberOfTellSelfInvocation); - - config.setInterval(deduplicationInterval); - config.setMaxPendingMsgs(msgCount); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctx, nodeConfiguration); - - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - long currentTimeMillis = System.currentTimeMillis(); - - List inputMsgs = getTbMsgs(deviceId, msgCount, currentTimeMillis, 500); - for (TbMsg msg : inputMsgs) { - node.onMsg(ctx, msg); - } - - TbMsg msgToReject = createMsg(deviceId, inputMsgs.get(inputMsgs.size() - 1).getMetaDataTs() + 2); - node.onMsg(ctx, msgToReject); - - awaitTellSelfLatch.await(); - - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); - ArgumentCaptor> failureCaptor = ArgumentCaptor.forClass(Consumer.class); - - verify(ctx, times(msgCount)).ack(any()); - verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); - verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - Assertions.assertEquals(inputMsgs.get(0), newMsgCaptor.getValue()); - } - - @Test - public void given_100_messages_strategy_last_then_verifyOutput() throws TbNodeException, ExecutionException, InterruptedException { - int wantedNumberOfTellSelfInvocation = 2; - int msgCount = 100; - awaitTellSelfLatch = new CountDownLatch(wantedNumberOfTellSelfInvocation); - invokeTellSelf(wantedNumberOfTellSelfInvocation); - - config.setStrategy(DeduplicationStrategy.LAST); - config.setInterval(deduplicationInterval); - config.setMaxPendingMsgs(msgCount); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctx, nodeConfiguration); - - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - long currentTimeMillis = System.currentTimeMillis(); - - List inputMsgs = getTbMsgs(deviceId, msgCount, currentTimeMillis, 500); - TbMsg msgWithLatestTs = getMsgWithLatestTs(inputMsgs); - - for (TbMsg msg : inputMsgs) { - node.onMsg(ctx, msg); - } - - TbMsg msgToReject = createMsg(deviceId, inputMsgs.get(inputMsgs.size() - 1).getMetaDataTs() + 2); - node.onMsg(ctx, msgToReject); - - awaitTellSelfLatch.await(); - - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); - ArgumentCaptor> failureCaptor = ArgumentCaptor.forClass(Consumer.class); - - verify(ctx, times(msgCount)).ack(any()); - verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); - verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - Assertions.assertEquals(msgWithLatestTs, newMsgCaptor.getValue()); - } - - @Test - public void given_100_messages_strategy_all_then_verifyOutput() throws TbNodeException, ExecutionException, InterruptedException { - int wantedNumberOfTellSelfInvocation = 2; - int msgCount = 100; - awaitTellSelfLatch = new CountDownLatch(wantedNumberOfTellSelfInvocation); - invokeTellSelf(wantedNumberOfTellSelfInvocation); - - config.setInterval(deduplicationInterval); - config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()); - config.setQueueName(HIGH_PRIORITY_QUEUE_NAME); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctx, nodeConfiguration); - - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - long currentTimeMillis = System.currentTimeMillis(); - - List inputMsgs = getTbMsgs(deviceId, msgCount, currentTimeMillis, 500); - for (TbMsg msg : inputMsgs) { - node.onMsg(ctx, msg); - } - - awaitTellSelfLatch.await(); - - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); - ArgumentCaptor> failureCaptor = ArgumentCaptor.forClass(Consumer.class); - - verify(ctx, times(msgCount)).ack(any()); - verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - - Assertions.assertEquals(1, newMsgCaptor.getAllValues().size()); - TbMsg outMessage = newMsgCaptor.getAllValues().get(0); - Assertions.assertEquals(getMergedData(inputMsgs), outMessage.getData()); - Assertions.assertEquals(deviceId, outMessage.getOriginator()); - Assertions.assertEquals(config.getOutMsgType(), outMessage.getType()); - Assertions.assertEquals(config.getQueueName(), outMessage.getQueueName()); - } - - @Test - public void given_100_messages_strategy_all_then_verifyOutput_2_packs() throws TbNodeException, ExecutionException, InterruptedException { - int wantedNumberOfTellSelfInvocation = 2; - int msgCount = 100; - awaitTellSelfLatch = new CountDownLatch(wantedNumberOfTellSelfInvocation); - invokeTellSelf(wantedNumberOfTellSelfInvocation, true, 3); - - config.setInterval(deduplicationInterval); - config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()); - config.setQueueName(HIGH_PRIORITY_QUEUE_NAME); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctx, nodeConfiguration); - - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - long currentTimeMillis = System.currentTimeMillis(); - - List firstMsgPack = getTbMsgs(deviceId, msgCount / 2, currentTimeMillis, 500); - for (TbMsg msg : firstMsgPack) { - node.onMsg(ctx, msg); - } - long firstPackDeduplicationPackEndTs = firstMsgPack.get(0).getMetaDataTs() + TimeUnit.SECONDS.toMillis(deduplicationInterval); - - List secondMsgPack = getTbMsgs(deviceId, msgCount / 2, firstPackDeduplicationPackEndTs, 500); - for (TbMsg msg : secondMsgPack) { - node.onMsg(ctx, msg); - } - - awaitTellSelfLatch.await(); - - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); - ArgumentCaptor> failureCaptor = ArgumentCaptor.forClass(Consumer.class); - - verify(ctx, times(msgCount)).ack(any()); - verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - - List resultMsgs = newMsgCaptor.getAllValues(); - Assertions.assertEquals(2, resultMsgs.size()); - - TbMsg firstMsg = resultMsgs.get(0); - Assertions.assertEquals(getMergedData(firstMsgPack), firstMsg.getData()); - Assertions.assertEquals(deviceId, firstMsg.getOriginator()); - Assertions.assertEquals(config.getOutMsgType(), firstMsg.getType()); - Assertions.assertEquals(config.getQueueName(), firstMsg.getQueueName()); - - TbMsg secondMsg = resultMsgs.get(1); - Assertions.assertEquals(getMergedData(secondMsgPack), secondMsg.getData()); - Assertions.assertEquals(deviceId, secondMsg.getOriginator()); - Assertions.assertEquals(config.getOutMsgType(), secondMsg.getType()); - Assertions.assertEquals(config.getQueueName(), secondMsg.getQueueName()); - } - - @Test - public void given_100_messages_strategy_last_then_verifyOutput_2_packs() throws TbNodeException, ExecutionException, InterruptedException { - int wantedNumberOfTellSelfInvocation = 2; - int msgCount = 100; - awaitTellSelfLatch = new CountDownLatch(wantedNumberOfTellSelfInvocation); - invokeTellSelf(wantedNumberOfTellSelfInvocation, true, 3); - - config.setInterval(deduplicationInterval); - config.setStrategy(DeduplicationStrategy.LAST); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctx, nodeConfiguration); - - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - long currentTimeMillis = System.currentTimeMillis(); - - List firstMsgPack = getTbMsgs(deviceId, msgCount / 2, currentTimeMillis, 500); - for (TbMsg msg : firstMsgPack) { - node.onMsg(ctx, msg); - } - long firstPackDeduplicationPackEndTs = firstMsgPack.get(0).getMetaDataTs() + TimeUnit.SECONDS.toMillis(deduplicationInterval); - TbMsg msgWithLatestTsInFirstPack = getMsgWithLatestTs(firstMsgPack); - - List secondMsgPack = getTbMsgs(deviceId, msgCount / 2, firstPackDeduplicationPackEndTs, 500); - for (TbMsg msg : secondMsgPack) { - node.onMsg(ctx, msg); - } - TbMsg msgWithLatestTsInSecondPack = getMsgWithLatestTs(secondMsgPack); - - awaitTellSelfLatch.await(); - - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); - ArgumentCaptor> failureCaptor = ArgumentCaptor.forClass(Consumer.class); - - verify(ctx, times(msgCount)).ack(any()); - verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - - List resultMsgs = newMsgCaptor.getAllValues(); - Assertions.assertEquals(2, resultMsgs.size()); - Assertions.assertTrue(resultMsgs.contains(msgWithLatestTsInFirstPack)); - Assertions.assertTrue(resultMsgs.contains(msgWithLatestTsInSecondPack)); - } - - private TbMsg getMsgWithLatestTs(List firstMsgPack) { - int indexOfLastMsgInArray = firstMsgPack.size() - 1; - int indexToSetMaxTs = new Random().nextInt(indexOfLastMsgInArray) + 1; - TbMsg currentMaxTsMsg = firstMsgPack.get(indexOfLastMsgInArray); - TbMsg newLastMsgOfArray = firstMsgPack.get(indexToSetMaxTs); - firstMsgPack.set(indexOfLastMsgInArray, newLastMsgOfArray); - firstMsgPack.set(indexToSetMaxTs, currentMaxTsMsg); - return currentMaxTsMsg; - } - - private List getTbMsgs(DeviceId deviceId, int msgCount, long currentTimeMillis, int initTsStep) { - List inputMsgs = new ArrayList<>(); - var ts = currentTimeMillis + initTsStep; - for (int i = 0; i < msgCount; i++) { - inputMsgs.add(createMsg(deviceId, ts)); - ts += 2; - } - return inputMsgs; - } - - private TbMsg createMsg(DeviceId deviceId, long ts) { - ObjectNode dataNode = JacksonUtil.newObjectNode(); - dataNode.put("deviceId", deviceId.getId().toString()); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("ts", String.valueOf(ts)); - return TbMsg.newMsg( - MAIN_QUEUE_NAME, - SessionMsgType.POST_TELEMETRY_REQUEST.name(), - deviceId, - metaData, - JacksonUtil.toString(dataNode)); - } - - private String getMergedData(List msgs) { - ArrayNode mergedData = JacksonUtil.OBJECT_MAPPER.createArrayNode(); - msgs.forEach(msg -> { - ObjectNode msgNode = JacksonUtil.newObjectNode(); - msgNode.set("msg", JacksonUtil.toJsonNode(msg.getData())); - msgNode.set("metadata", JacksonUtil.valueToTree(msg.getMetaData().getData())); - mergedData.add(msgNode); - }); - return JacksonUtil.toString(mergedData); - } - -} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index 475a98e2a2..1badecfa92 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -174,7 +174,16 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - Assertions.assertEquals(inputMsgs.get(0), newMsgCaptor.getValue()); + + TbMsg firstMsg = inputMsgs.get(0); + TbMsg actualMsg = newMsgCaptor.getValue(); + // msg ids should be different because we create new msg before enqueueForTellNext + Assertions.assertNotEquals(firstMsg.getId(), actualMsg.getId()); + Assertions.assertEquals(firstMsg.getOriginator(), actualMsg.getOriginator()); + Assertions.assertEquals(firstMsg.getCustomerId(), actualMsg.getCustomerId()); + Assertions.assertEquals(firstMsg.getData(), actualMsg.getData()); + Assertions.assertEquals(firstMsg.getMetaData(), actualMsg.getMetaData()); + Assertions.assertEquals(firstMsg.getType(), actualMsg.getType()); } @Test @@ -213,7 +222,15 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); - Assertions.assertEquals(msgWithLatestTs, newMsgCaptor.getValue()); + + TbMsg actualMsg = newMsgCaptor.getValue(); + // msg ids should be different because we create new msg before enqueueForTellNext + Assertions.assertNotEquals(msgWithLatestTs.getId(), actualMsg.getId()); + Assertions.assertEquals(msgWithLatestTs.getOriginator(), actualMsg.getOriginator()); + Assertions.assertEquals(msgWithLatestTs.getCustomerId(), actualMsg.getCustomerId()); + Assertions.assertEquals(msgWithLatestTs.getData(), actualMsg.getData()); + Assertions.assertEquals(msgWithLatestTs.getMetaData(), actualMsg.getMetaData()); + Assertions.assertEquals(msgWithLatestTs.getType(), actualMsg.getType()); } @Test @@ -350,8 +367,24 @@ public class TbMsgDeduplicationNodeTest { List resultMsgs = newMsgCaptor.getAllValues(); Assertions.assertEquals(2, resultMsgs.size()); - Assertions.assertTrue(resultMsgs.contains(msgWithLatestTsInFirstPack)); - Assertions.assertTrue(resultMsgs.contains(msgWithLatestTsInSecondPack)); + + // verify that newMsg is called but content of messages is the same as in the last msg for the first pack. + TbMsg actualMsg = resultMsgs.get(0); + Assertions.assertNotEquals(msgWithLatestTsInFirstPack.getId(), actualMsg.getId()); + Assertions.assertEquals(msgWithLatestTsInFirstPack.getOriginator(), actualMsg.getOriginator()); + Assertions.assertEquals(msgWithLatestTsInFirstPack.getCustomerId(), actualMsg.getCustomerId()); + Assertions.assertEquals(msgWithLatestTsInFirstPack.getData(), actualMsg.getData()); + Assertions.assertEquals(msgWithLatestTsInFirstPack.getMetaData(), actualMsg.getMetaData()); + Assertions.assertEquals(msgWithLatestTsInFirstPack.getType(), actualMsg.getType()); + + // verify that newMsg is called but content of messages is the same as in the last msg for the second pack. + actualMsg = resultMsgs.get(1); + Assertions.assertNotEquals(msgWithLatestTsInSecondPack.getId(), actualMsg.getId()); + Assertions.assertEquals(msgWithLatestTsInSecondPack.getOriginator(), actualMsg.getOriginator()); + Assertions.assertEquals(msgWithLatestTsInSecondPack.getCustomerId(), actualMsg.getCustomerId()); + Assertions.assertEquals(msgWithLatestTsInSecondPack.getData(), actualMsg.getData()); + Assertions.assertEquals(msgWithLatestTsInSecondPack.getMetaData(), actualMsg.getMetaData()); + Assertions.assertEquals(msgWithLatestTsInSecondPack.getType(), actualMsg.getType()); } private TbMsg getMsgWithLatestTs(List firstMsgPack) { From 64264a7834c16427af80e9bb82193a503eb584dc Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 17 Apr 2023 12:07:03 +0300 Subject: [PATCH 077/168] UI: Refactoring color picker --- .../color-picker/color-picker.component.html | 30 ++--- .../color-picker/color-picker.component.scss | 52 ++------- .../color-picker/color-picker.component.ts | 109 ++++++++---------- .../dialog/color-picker-dialog.component.html | 6 +- .../dialog/color-picker-dialog.component.ts | 8 -- 5 files changed, 73 insertions(+), 132 deletions(-) diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html index 1005fa4a58..b5082530cd 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html @@ -17,29 +17,21 @@ --> -
    -
    -
    - -
    -
    +
    + +
    -
    -
    -
    - - - - - -
    -
    - -
    +
    +
    + + + +
    +
    +
    -
    diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index 38b55346aa..ddd870c7d0 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -14,17 +14,17 @@ * limitations under the License. */ :host { - display: block; width: 100%; - max-width: 320px; - min-width: 100%; - border-radius: 2px; - background: #fff; + display: flex; + flex-direction: column; ::ng-deep { .saturation-component { - height: 200px; + height: 100%; + min-height: 200px; + max-height: 300px; border-radius: 8px; + margin-bottom: 16px; div.pointer { border-width: 2px; width: 16px; @@ -56,25 +56,9 @@ } } - - -.controls { - padding: 15px 0; -} - -.controls-row { - display: table; - width: 100%; -} - -.column { - display: table-cell; - vertical-align: middle; -} - -.hue-alpha .column:first-child { - width: 42px; - padding: 0 10px 0 0; +.control-component { + max-height: 48px; + margin-bottom: 16px; } indicator-component { @@ -90,17 +74,8 @@ indicator-component { } } -hue-component { - margin-bottom: 16px; -} - color-presets-component { border-top: 1px solid #d0d0d0; - padding: 12px; - - ::ng-deep .presets-row { - padding: 12px 0 0; - } } .type-btn { @@ -114,12 +89,3 @@ color-presets-component { background-color: #eee; } } - -.type-column { - width: 25px; - text-align: right; -} - -.presentation { - padding: 12px 0 0; -} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts index ab8fb847dd..dd7afee70a 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -14,20 +14,10 @@ /// limitations under the License. /// -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - SimpleChanges -} from '@angular/core'; +import { Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; import { Color, ColorPickerControl } from '@iplab/ngx-color-picker'; import { Subscription } from 'rxjs'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; export enum ColorType { hex = 'hex', @@ -43,89 +33,90 @@ export enum ColorType { selector: `tb-color-picker`, templateUrl: `./color-picker.component.html`, styleUrls: [`./color-picker.component.scss`], - changeDetection: ChangeDetectionStrategy.OnPush + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ColorPickerComponent), + multi: true + } + ] }) -export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { - - public selectedPresentation = 0; - public presentations = [ColorType.hex, ColorType.rgb, ColorType.rgba, ColorType.hsla, ColorType.hsl]; +export class ColorPickerComponent implements OnInit, ControlValueAccessor, OnDestroy { - @Input() - public color: string; + selectedPresentation = 0; + presentations = [ColorType.hex, ColorType.rgba, ColorType.hsla]; + control = new ColorPickerControl(); - @Input() - public control: ColorPickerControl; - - @Output() - public colorChange: EventEmitter = new EventEmitter(false); + private modelValue: string; private subscriptions: Array = []; - constructor(private readonly cdr: ChangeDetectorRef) { + private propagateChange = null; + + constructor() { } public ngOnInit(): void { - if (!this.control) { - this.control = new ColorPickerControl(); - } + this.subscriptions.push( + this.control.valueChanges.subscribe(value => { + if (this.modelValue) { + this.updateModel(); + } + }) + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + writeValue(value: string): void { + this.control. setValueFrom(value || '#fff'); + this.modelValue = value; if (this.control.initType === ColorType.hexa) { this.control.initType = ColorType.hex; + } else if (this.control.initType === ColorType.rgb) { + this.control.initType = ColorType.rgba; + } else if (this.control.initType === ColorType.hsl) { + this.control.initType = ColorType.hsla; } this.selectedPresentation = this.presentations.indexOf(this.control.initType); - - if (this.color) { - this.control.setValueFrom(this.color); - } - - this.subscriptions.push( - this.control.valueChanges.subscribe((value) => { - this.cdr.markForCheck(); - this.colorChange.emit(this.getValueByType(value, this.presentations[this.selectedPresentation])); - }) - ); } - changeColorFormat(event: Event) { - this.colorChange.emit(this.getValueByType(this.control.value, this.presentations[this.selectedPresentation])); + private updateModel() { + const color: string = this.getValueByType(this.control.value, this.presentations[this.selectedPresentation]); + if (this.modelValue !== color) { + this.modelValue = color; + this.propagateChange(color); + } } public ngOnDestroy(): void { - this.cdr.detach(); this.subscriptions.forEach((subscription) => subscription.unsubscribe()); this.subscriptions.length = 0; } - public ngOnChanges(changes: SimpleChanges): void { - if (this.color && this.control && - this.getValueByType(this.control.value, this.presentations[this.selectedPresentation]) !== this.color) { - this.control.setValueFrom(this.color); - } - } - public changePresentation(): void { this.selectedPresentation = this.selectedPresentation === this.presentations.length - 1 ? 0 : this.selectedPresentation + 1; - this.colorChange.emit(this.getValueByType(this.control.value, this.presentations[this.selectedPresentation])); - this.cdr.markForCheck(); + this.updateModel(); } getValueByType(color: Color, type: ColorType): string { switch (type) { - case ColorType.hex || ColorType.hexa: + case ColorType.hex: return color.toHexString(this.control.value.getRgba().getAlpha() !== 1); - case ColorType.rgb: - return color.toRgbString(); case ColorType.rgba: - return color.toRgbaString(); - case ColorType.hsl: - return color.toHslString(); + return this.control.value.getRgba().getAlpha() !== 1 ? color.toRgbaString() : color.toRgbString(); case ColorType.hsla: - return color.toHslaString(); + return this.control.value.getRgba().getAlpha() !== 1 ? color.toHslaString() : color.toHslString(); default: return color.toRgbaString(); } } - } diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index aedeff35b2..de733b2a20 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -15,9 +15,9 @@ limitations under the License. --> - -
    - + +
    +
    diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 8a9d5cbdf8..0938db05db 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -29,7 +29,6 @@ import { } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; -import { ColorPickerControl } from '@iplab/ngx-color-picker'; export interface ColorPickerDialogData { color: string; @@ -48,8 +47,6 @@ export class ColorPickerDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ColorPickerDialogData, @@ -71,11 +68,6 @@ export class ColorPickerDialogComponent extends DialogComponent Date: Mon, 17 Apr 2023 14:01:15 +0300 Subject: [PATCH 078/168] UI: Refactoring color picker --- .../color-picker/color-picker.component.html | 34 ++--- .../color-picker/color-picker.component.scss | 116 ++++++++++-------- .../color-picker/color-picker.component.ts | 20 +-- .../dialog/color-picker-dialog.component.html | 4 +- .../dialog/color-picker-dialog.component.ts | 29 +---- 5 files changed, 101 insertions(+), 102 deletions(-) diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html index b5082530cd..d1432d27f5 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html @@ -17,21 +17,25 @@ --> -
    - -
    - - -
    +
    + + +
    + +
    +
    -
    -
    - - - -
    -
    - -
    +
    +
    + + +
    +
    +
    diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index ddd870c7d0..6d3978550a 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -17,75 +17,87 @@ width: 100%; display: flex; flex-direction: column; + gap: 16px; - ::ng-deep { - .saturation-component { - height: 100%; - min-height: 200px; - max-height: 300px; - border-radius: 8px; - margin-bottom: 16px; - div.pointer { - border-width: 2px; - width: 16px; - height: 16px; - } - } + .saturation-component { + height: 100%; + min-height: 200px; + max-height: 300px; + border-radius: 8px; + } - indicator-component { - svg { - vertical-align: -70% !important; - } + .control-component { + max-height: 48px; + display: flex; + gap: 16px; + + .indicator-component { + height: 48px; + width: 48px; + border-radius: 8px; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMUAFg9Gm0AAAAPSURBVAjXY2D4jxXhEgYAfr8P8QhChVEAAAAASUVORK5CYII=') repeat; } .hue-alpha-range { - alpha-component, hue-component { + display: flex; + justify-content: space-between; + flex-direction: column; + flex: 1; + + > * { height: 18px; + border-radius: 9px; border: 1px solid rgba(0, 0, 0, 0.1); - .pointer { - height: 18px; - width: 18px; - background: none; - border: 2px solid #fff; - } - .gradient-color { - border-radius: 24px; - } } } } -} -.control-component { - max-height: 48px; - margin-bottom: 16px; -} + .color-input-block { + display: flex; -indicator-component { - height: 48px; - width: 48px; - border-radius: 8px; - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMUAFg9Gm0AAAAPSURBVAjXY2D4jxXhEgYAfr8P8QhChVEAAAAASUVORK5CYII=') repeat; -} + .color-input { + flex: 1; + } -.hue-alpha-range { - hue-component, alpha-component { - border-radius: 24px; + .type-btn { + height: 26px; + width: 20px; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAMAAAAootjDAAAAM1BMVEUAAAAzMzMzMzMzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzMyMjIrKyszMzPF8UZlAAAAEHRSTlMA1fHr4ZxxSRP45sG+sCkGH2+Z6QAAAHJJREFUKM+9kkkSgCAQA0FEVLb5/2tViqgQvNrHviSzKGCt6nDGuNass8i8NsrLiX+bZbrUtDwm7VLYE0zWUtEZ+RvUZpEvN8YhH9QmQRoC8kFpEnVHVP/DJUZVeSAem5fDKxwtms/BR+PT8gN8vwk/0wE1gQzNVYryIwAAAABJRU5ErkJggg==') no-repeat center; + background-size: 6px 12px; + + &:hover { + background-color: #eee; + } + } } } -color-presets-component { - border-top: 1px solid #d0d0d0; -} +:host ::ng-deep { + .saturation-component { + .pointer { + border-width: 2px; + width: 16px; + height: 16px; + } + } -.type-btn { - display: inline-block; - height: 20px; - width: 20px; - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAMAAAAootjDAAAAM1BMVEUAAAAzMzMzMzMzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzMyMjIrKyszMzPF8UZlAAAAEHRSTlMA1fHr4ZxxSRP45sG+sCkGH2+Z6QAAAHJJREFUKM+9kkkSgCAQA0FEVLb5/2tViqgQvNrHviSzKGCt6nDGuNass8i8NsrLiX+bZbrUtDwm7VLYE0zWUtEZ+RvUZpEvN8YhH9QmQRoC8kFpEnVHVP/DJUZVeSAem5fDKxwtms/BR+PT8gN8vwk/0wE1gQzNVYryIwAAAABJRU5ErkJggg==') no-repeat center; - background-size: 6px 12px; + indicator-component { + svg { + vertical-align: -70% !important; + } + } - &:hover { - background-color: #eee; + .hue-alpha-range { + alpha-component, hue-component { + .pointer { + height: 18px; + width: 18px; + background: none; + border: 2px solid #fff; + } + .gradient-color { + border-radius: 9px; + } + } } } diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts index dd7afee70a..ce49310b03 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; +import { Component, forwardRef, OnDestroy } from '@angular/core'; import { Color, ColorPickerControl } from '@iplab/ngx-color-picker'; import { Subscription } from 'rxjs'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -41,7 +41,7 @@ export enum ColorType { } ] }) -export class ColorPickerComponent implements OnInit, ControlValueAccessor, OnDestroy { +export class ColorPickerComponent implements ControlValueAccessor, OnDestroy { selectedPresentation = 0; presentations = [ColorType.hex, ColorType.rgba, ColorType.hsla]; @@ -51,16 +51,17 @@ export class ColorPickerComponent implements OnInit, ControlValueAccessor, OnDes private subscriptions: Array = []; - private propagateChange = null; + private propagateChange = (v: any) => {}; - constructor() { - } + private setValue = false; - public ngOnInit(): void { + constructor() { this.subscriptions.push( - this.control.valueChanges.subscribe(value => { - if (this.modelValue) { + this.control.valueChanges.subscribe(() => { + if (!this.setValue) { this.updateModel(); + } else { + this.setValue = false; } }) ); @@ -74,7 +75,8 @@ export class ColorPickerComponent implements OnInit, ControlValueAccessor, OnDes } writeValue(value: string): void { - this.control. setValueFrom(value || '#fff'); + this.setValue = !!value; + this.control.setValueFrom(value || '#fff'); this.modelValue = value; if (this.control.initType === ColorType.hexa) { diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index de733b2a20..eaec4c0b5e 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -15,8 +15,8 @@ limitations under the License. --> - -
    + +
    diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 0938db05db..44959023f4 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -14,19 +14,11 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher } from '@angular/material/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { - FormGroupDirective, - NgForm, - UntypedFormBuilder, - UntypedFormControl, - UntypedFormGroup, - Validators -} from '@angular/forms'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; @@ -37,22 +29,18 @@ export interface ColorPickerDialogData { @Component({ selector: 'tb-color-picker-dialog', templateUrl: './color-picker-dialog.component.html', - providers: [{provide: ErrorStateMatcher, useExisting: ColorPickerDialogComponent}], styleUrls: [] }) export class ColorPickerDialogComponent extends DialogComponent - implements OnInit, ErrorStateMatcher { + implements OnInit { - colorPickerFormGroup: UntypedFormGroup; - - submitted = false; + colorPickerFormGroup: FormGroup; constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ColorPickerDialogData, - @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, - public fb: UntypedFormBuilder,) { + public fb: FormBuilder) { super(store, router, dialogRef); } @@ -62,18 +50,11 @@ export class ColorPickerDialogComponent extends DialogComponent Date: Mon, 17 Apr 2023 16:24:32 +0300 Subject: [PATCH 079/168] UI: Fixed hue-alpha-range overflow --- .../shared/components/color-picker/color-picker.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index 6d3978550a..f71707195a 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -46,6 +46,7 @@ > * { height: 18px; + overflow: hidden; border-radius: 9px; border: 1px solid rgba(0, 0, 0, 0.1); } From a5cfe079996e53d38b3a57038c7013915878a00f Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 17 Apr 2023 16:36:33 +0300 Subject: [PATCH 080/168] Edge - improved logic of interruption of sending downlink tasks --- .../service/edge/rpc/EdgeGrpcService.java | 2 +- .../service/edge/rpc/EdgeGrpcSession.java | 58 +++++++++---------- .../service/edge/rpc/EdgeSessionState.java | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index f273517b74..5171a6037f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -288,7 +288,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i if (session != null) { boolean success = false; if (session.isConnected()) { - session.startSyncProcess(tenantId, edgeId, true); + session.startSyncProcess(true); success = true; } clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, success)); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 9e3a22f64f..33aed92682 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -148,7 +148,7 @@ public final class EdgeGrpcSession implements Closeable { if (requestMsg.getSyncRequestMsg().hasFullSync()) { fullSync = requestMsg.getSyncRequestMsg().getFullSync(); } - startSyncProcess(edge.getTenantId(), edge.getId(), fullSync); + startSyncProcess(fullSync); } else { syncCompleted = true; } @@ -192,10 +192,10 @@ public final class EdgeGrpcSession implements Closeable { }; } - public void startSyncProcess(TenantId tenantId, EdgeId edgeId, boolean fullSync) { - log.trace("[{}][{}] Staring edge sync process", tenantId, edgeId); + public void startSyncProcess(boolean fullSync) { + log.trace("[{}][{}][{}] Staring edge sync process", edge.getTenantId(), edge.getId(), this.sessionId); syncCompleted = false; - interruptGeneralProcessingOnSync(tenantId, edgeId); + interruptGeneralProcessingOnSync(); doSync(new EdgeSyncCursor(ctx, edge, fullSync)); } @@ -221,9 +221,9 @@ public final class EdgeGrpcSession implements Closeable { .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setSyncCompletedMsg(SyncCompletedMsg.newBuilder().build()) .build(); - Futures.addCallback(sendDownlinkMsgsPack(Collections.singletonList(syncCompleteDownlinkMsg)), new FutureCallback() { + Futures.addCallback(sendDownlinkMsgsPack(Collections.singletonList(syncCompleteDownlinkMsg)), new FutureCallback<>() { @Override - public void onSuccess(Void result) { + public void onSuccess(Boolean isInterrupted) { syncCompleted = true; ctx.getClusterService().onEdgeEventUpdate(edge.getTenantId(), edge.getId()); } @@ -272,7 +272,7 @@ public final class EdgeGrpcSession implements Closeable { } if (sessionState.getPendingMsgsMap().isEmpty()) { log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey()); - stopCurrentSendDownlinkMsgsTask(null); + stopCurrentSendDownlinkMsgsTask(false); } } catch (Exception e) { log.error("[{}] Can't process downlink response message [{}]", this.sessionId, msg, e); @@ -367,14 +367,19 @@ public final class EdgeGrpcSession implements Closeable { if (isConnected() && !pageData.getData().isEmpty()) { log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size()); List downlinkMsgsPack = convertToDownlinkMsgsPack(pageData.getData()); - Futures.addCallback(sendDownlinkMsgsPack(downlinkMsgsPack), new FutureCallback() { + Futures.addCallback(sendDownlinkMsgsPack(downlinkMsgsPack), new FutureCallback<>() { @Override - public void onSuccess(@Nullable Void tmp) { - if (isConnected() && pageData.hasNext()) { - processEdgeEvents(fetcher, pageLink.nextPageLink(), result); + public void onSuccess(@Nullable Boolean isInterrupted) { + if (Boolean.TRUE.equals(isInterrupted)) { + log.debug("[{}][{}][{}] Send downlink messages task was interrupted", edge.getTenantId(), edge.getId(), sessionId); + result.set(null); } else { - UUID ifOffset = pageData.getData().get(pageData.getData().size() - 1).getUuidId(); - result.set(ifOffset); + if (isConnected() && pageData.hasNext()) { + processEdgeEvents(fetcher, pageLink.nextPageLink(), result); + } else { + UUID ifOffset = pageData.getData().get(pageData.getData().size() - 1).getUuidId(); + result.set(ifOffset); + } } } @@ -394,7 +399,7 @@ public final class EdgeGrpcSession implements Closeable { } } - private ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) { + private ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) { interruptPreviousSendDownlinkMsgsTask(); sessionState.setSendDownlinkMsgsFuture(SettableFuture.create()); @@ -433,14 +438,14 @@ public final class EdgeGrpcSession implements Closeable { } else { log.warn("[{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}", this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy); - stopCurrentSendDownlinkMsgsTask(null); + stopCurrentSendDownlinkMsgsTask(false); } } else { - stopCurrentSendDownlinkMsgsTask(null); + stopCurrentSendDownlinkMsgsTask(false); } } catch (Exception e) { log.warn("[{}] Failed to send downlink msgs. Error msg {}", this.sessionId, e.getMessage(), e); - stopCurrentSendDownlinkMsgsTask(e); + stopCurrentSendDownlinkMsgsTask(true); } }; @@ -688,23 +693,18 @@ public final class EdgeGrpcSession implements Closeable { } private void interruptPreviousSendDownlinkMsgsTask() { - String msg = String.format("[%s] Previous send downlink future was not properly completed, stopping it now!", this.sessionId); - stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg)); + log.debug("[{}][{}][{}] Previous send downlink future was not properly completed, stopping it now!", edge.getTenantId(), edge.getId(), this.sessionId); + stopCurrentSendDownlinkMsgsTask(true); } - private void interruptGeneralProcessingOnSync(TenantId tenantId, EdgeId edgeId) { - String msg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId); - stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg)); + private void interruptGeneralProcessingOnSync() { + log.debug("[{}][{}][{}] Sync process started. General processing interrupted!", edge.getTenantId(), edge.getId(), this.sessionId); + stopCurrentSendDownlinkMsgsTask(true); } - public void stopCurrentSendDownlinkMsgsTask(Exception e) { + public void stopCurrentSendDownlinkMsgsTask(Boolean isInterrupted) { if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { - if (e != null) { - log.debug(e.getMessage()); - sessionState.getSendDownlinkMsgsFuture().setException(e); - } else { - sessionState.getSendDownlinkMsgsFuture().set(null); - } + sessionState.getSendDownlinkMsgsFuture().set(isInterrupted); } if (sessionState.getScheduledSendDownlinkTask() != null) { sessionState.getScheduledSendDownlinkTask().cancel(true); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java index 59910c829f..09a7c5add8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java @@ -28,6 +28,6 @@ import java.util.concurrent.ScheduledFuture; public class EdgeSessionState { private final Map pendingMsgsMap = Collections.synchronizedMap(new LinkedHashMap<>()); - private SettableFuture sendDownlinkMsgsFuture; + private SettableFuture sendDownlinkMsgsFuture; private ScheduledFuture scheduledSendDownlinkTask; } From 9c1e57a8c134a7db86cdbc177054fcb45e5e9d6f Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 17 Apr 2023 18:26:11 +0300 Subject: [PATCH 081/168] Add correct validation message for X.509 certificate in Device Profile --- .../server/dao/device/DeviceProfileServiceImpl.java | 5 ++++- .../device-profile-provision-configuration.component.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index a5b3e56341..391695f2bf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -160,9 +160,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Mon, 17 Apr 2023 19:09:23 +0300 Subject: [PATCH 082/168] UI: Tenant admin home page --- .../widget_bundles/home_page_widgets.json | 48 ++++- ui-ngx/src/app/core/http/public-api.ts | 1 + .../src/app/core/http/usage-info.service.ts | 37 ++++ .../app/core/http/user-settings.service.ts | 12 +- ui-ngx/src/app/core/services/menu.service.ts | 168 +++++++++++------- .../widget/lib/flot-widget.models.ts | 1 + .../home/components/widget/lib/flot-widget.ts | 2 +- .../configured-features.component.scss | 14 -- .../configured-features.component.ts | 2 +- .../home-page/doc-links-widget.component.html | 16 +- .../home-page/doc-links-widget.component.ts | 6 +- .../getting-started-widget.component.html | 19 +- .../lib/home-page/home-page-widget.scss | 70 ++++++++ .../lib/home-page/home-page-widgets.module.ts | 10 +- ...onent.scss => links-widget.component.scss} | 52 +----- .../quick-links-widget.component.html | 51 ++++++ .../home-page/quick-links-widget.component.ts | 162 +++++++++++++++++ .../home-page/toggle-header.component.html | 11 +- .../home-page/toggle-header.component.scss | 51 +++++- .../lib/home-page/toggle-header.component.ts | 57 +++--- .../usage-info-widget.component.html | 89 ++++++++++ .../usage-info-widget.component.scss | 108 +++++++++++ .../home-page/usage-info-widget.component.ts | 111 ++++++++++++ .../lib/home-page/version-info.component.scss | 18 -- .../lib/home-page/version-info.component.ts | 2 +- .../chart/flot-key-settings.component.html | 6 +- .../chart/flot-key-settings.component.ts | 20 ++- ...quick-links-widget-settings.component.html | 23 +++ .../quick-links-widget-settings.component.ts | 52 ++++++ .../lib/settings/widget-settings.module.ts | 12 +- .../home-links/tenant_admin_home_page.raw | 89 +++++++++- ui-ngx/src/app/shared/models/public-api.ts | 1 + ui-ngx/src/app/shared/models/usage.models.ts | 39 ++++ .../app/shared/models/user-settings.models.ts | 4 + .../src/app/shared/pipe/short-number.pipe.ts | 53 ++++++ ui-ngx/src/app/shared/shared.module.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 11 ++ 37 files changed, 1212 insertions(+), 223 deletions(-) create mode 100644 ui-ngx/src/app/core/http/usage-info.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widget.scss rename ui-ngx/src/app/modules/home/components/widget/lib/home-page/{doc-links-widget.component.scss => links-widget.component.scss} (73%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/quick-links-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/quick-links-widget-settings.component.ts create mode 100644 ui-ngx/src/app/shared/models/usage.models.ts create mode 100644 ui-ngx/src/app/shared/pipe/short-number.pipe.ts diff --git a/application/src/main/data/json/system/widget_bundles/home_page_widgets.json b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json index fc5b13154c..62eb3d4d6e 100644 --- a/application/src/main/data/json/system/widget_bundles/home_page_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json @@ -8,6 +8,25 @@ "name": "Home page widgets" }, "widgetTypes": [ + { + "alias": "getting_started", + "name": "Getting started", + "image": null, + "description": null, + "descriptor": { + "type": "static", + "sizeX": 7.5, + "sizeY": 6.5, + "resources": [], + "templateHtml": "\n\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n}", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "", + "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Getting started\",\"dropShadow\":true}" + } + }, { "alias": "documentation_links", "name": "Documentation links", @@ -24,12 +43,12 @@ "settingsSchema": "", "dataKeySettingsSchema": "", "settingsDirective": "tb-doc-links-widget-settings", - "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true}" + "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" } }, { - "alias": "getting_started", - "name": "Getting started", + "alias": "usage_info", + "name": "Usage info", "image": null, "description": null, "descriptor": { @@ -37,13 +56,32 @@ "sizeX": 7.5, "sizeY": 6.5, "resources": [], - "templateHtml": "\n\n", + "templateHtml": "\n\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}", "settingsSchema": "", "dataKeySettingsSchema": "", "settingsDirective": "", - "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Getting started\",\"dropShadow\":true}" + "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Usage info\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" + } + }, + { + "alias": "quick_links", + "name": "Quick links", + "image": null, + "description": null, + "descriptor": { + "type": "static", + "sizeX": 7.5, + "sizeY": 3, + "resources": [], + "templateHtml": "\n\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n}", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "tb-quick-links-widget-settings", + "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Quick links\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" } } ] diff --git a/ui-ngx/src/app/core/http/public-api.ts b/ui-ngx/src/app/core/http/public-api.ts index 5a511b8f60..d96df82055 100644 --- a/ui-ngx/src/app/core/http/public-api.ts +++ b/ui-ngx/src/app/core/http/public-api.ts @@ -42,3 +42,4 @@ export * from './ui-settings.service'; export * from './user.service'; export * from './user-settings.service'; export * from './widget.service'; +export * from './usage-info.service'; diff --git a/ui-ngx/src/app/core/http/usage-info.service.ts b/ui-ngx/src/app/core/http/usage-info.service.ts new file mode 100644 index 0000000000..a0869c5dba --- /dev/null +++ b/ui-ngx/src/app/core/http/usage-info.service.ts @@ -0,0 +1,37 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { UsageInfo } from '@shared/models/usage.models'; + +@Injectable({ + providedIn: 'root' +}) +export class UsageInfoService { + + constructor( + private http: HttpClient + ) {} + + public getUsageInfo(config?: RequestConfig): Observable { + return this.http.get('/api/usage', + defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/http/user-settings.service.ts b/ui-ngx/src/app/core/http/user-settings.service.ts index 55ce524750..ed6fc42a0c 100644 --- a/ui-ngx/src/app/core/http/user-settings.service.ts +++ b/ui-ngx/src/app/core/http/user-settings.service.ts @@ -19,7 +19,7 @@ import { Observable } from 'rxjs'; import { DocumentationLink, DocumentationLinks, GettingStarted, - initialUserSettings, + initialUserSettings, QuickLinks, UserSettings, UserSettingsType } from '@shared/models/user-settings.models'; @@ -67,6 +67,16 @@ export class UserSettingsService { defaultHttpOptionsFromConfig(config)); } + public getQuickLinks(config?: RequestConfig): Observable { + return this.http.get(`/api/user/settings/${UserSettingsType.QUICK_LINKS}`, + defaultHttpOptionsFromConfig(config)); + } + + public updateQuickLinks(quickLinks: QuickLinks, config?: RequestConfig): Observable { + return this.http.put(`/api/user/settings/${UserSettingsType.QUICK_LINKS}`, quickLinks, + defaultHttpOptionsFromConfig(config)); + } + public getGettingStarted(config?: RequestConfig): Observable { return this.http.get(`/api/user/settings/${UserSettingsType.GETTING_STARTED}`, defaultHttpOptionsFromConfig(config)); diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 82ad8221fa..99f4a557a7 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -18,11 +18,10 @@ import { Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { AppState } from '../core.state'; import { getCurrentOpenedMenuSections, selectAuth, selectIsAuthenticated } from '../auth/auth.selectors'; -import { filter, take } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; import { HomeSection, MenuSection } from '@core/services/menu.models'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Authority } from '@shared/models/authority.enum'; -import { guid } from '@core/utils'; import { AuthState } from '@core/auth/auth.models'; import { NavigationEnd, Router } from '@angular/router'; @@ -34,6 +33,9 @@ export class MenuService { currentMenuSections: Array; menuSections$: Subject> = new BehaviorSubject>([]); homeSections$: Subject> = new BehaviorSubject>([]); + availableMenuLinks$ = this.menuSections$.pipe( + map((items) => this.allMenuLinks(items)) + ); constructor(private store: Store, private router: Router) { @@ -91,21 +93,21 @@ export class MenuService { const sections: Array = []; sections.push( { - id: guid(), + id: 'home', name: 'home.home', type: 'link', path: '/home', icon: 'home' }, { - id: guid(), + id: 'tenants', name: 'tenant.tenants', type: 'link', path: '/tenants', icon: 'supervisor_account' }, { - id: guid(), + id: 'tenantProfiles', name: 'tenant-profile.tenant-profiles', type: 'link', path: '/tenantProfiles', @@ -113,21 +115,21 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'resources', name: 'admin.resources', type: 'toggle', path: '/resources', icon: 'folder', pages: [ { - id: guid(), + id: 'resourcesWidgetsBundles', name: 'widget.widget-library', type: 'link', path: '/resources/widgets-bundles', icon: 'now_widgets' }, { - id: guid(), + id: 'resourcesResourcesLibrary', name: 'resource.resources-library', type: 'link', path: '/resources/resources-library', @@ -137,7 +139,7 @@ export class MenuService { ] }, { - id: guid(), + id: 'notification', name: 'notification.notification-center', type: 'link', path: '/notification', @@ -145,28 +147,28 @@ export class MenuService { isMdiIcon: true, pages: [ { - id: guid(), + id: 'notificationInbox', name: 'notification.inbox', type: 'link', path: '/notification/inbox', icon: 'inbox' }, { - id: guid(), + id: 'notificationSent', name: 'notification.sent', type: 'link', path: '/notification/sent', icon: 'outbox' }, { - id: guid(), + id: 'notificationRecipients', name: 'notification.recipients', type: 'link', path: '/notification/recipients', icon: 'contacts' }, { - id: guid(), + id: 'notificationTemplates', name: 'notification.templates', type: 'link', path: '/notification/templates', @@ -174,7 +176,7 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'notificationRules', name: 'notification.rules', type: 'link', path: '/notification/rules', @@ -184,28 +186,28 @@ export class MenuService { ] }, { - id: guid(), + id: 'settings', name: 'admin.settings', type: 'link', path: '/settings', icon: 'settings', pages: [ { - id: guid(), + id: 'settingsGeneral', name: 'admin.general', type: 'link', path: '/settings/general', icon: 'settings_applications' }, { - id: guid(), + id: 'settingsOutgoingMail', name: 'admin.outgoing-mail', type: 'link', path: '/settings/outgoing-mail', icon: 'mail' }, { - id: guid(), + id: 'settingsNotifications', name: 'admin.notifications', type: 'link', path: '/settings/notifications', @@ -213,7 +215,7 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'settingsQueues', name: 'admin.queues', type: 'link', path: '/settings/queues', @@ -222,21 +224,21 @@ export class MenuService { ] }, { - id: guid(), + id: 'securitySettings', name: 'security.security', type: 'toggle', path: '/security-settings', icon: 'security', pages: [ { - id: guid(), + id: 'securitySettingsGeneral', name: 'admin.general', type: 'link', path: '/security-settings/general', icon: 'settings_applications' }, { - id: guid(), + id: 'securitySettings2fa', name: 'admin.2fa.2fa', type: 'link', path: '/security-settings/2fa', @@ -244,7 +246,7 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'securitySettingsOauth2', name: 'admin.oauth2.oauth2', type: 'link', path: '/security-settings/oauth2', @@ -340,49 +342,49 @@ export class MenuService { const sections: Array = []; sections.push( { - id: guid(), + id: 'home', name: 'home.home', type: 'link', path: '/home', icon: 'home' }, { - id: guid(), + id: 'alarms', name: 'alarm.alarms', type: 'link', path: '/alarms', icon: 'notifications' }, { - id: guid(), + id: 'dashboards', name: 'dashboard.dashboards', type: 'link', path: '/dashboards', icon: 'dashboards' }, { - id: guid(), + id: 'entities', name: 'entity.entities', type: 'toggle', path: '/entities', icon: 'category', pages: [ { - id: guid(), + id: 'entitiesDevices', name: 'device.devices', type: 'link', path: '/entities/devices', icon: 'devices_other' }, { - id: guid(), + id: 'entitiesAssets', name: 'asset.assets', type: 'link', path: '/entities/assets', icon: 'domain' }, { - id: guid(), + id: 'entitiesEntityViews', name: 'entity-view.entity-views', type: 'link', path: '/entities/entityViews', @@ -391,14 +393,14 @@ export class MenuService { ] }, { - id: guid(), + id: 'profiles', name: 'profiles.profiles', type: 'toggle', path: '/profiles', icon: 'badge', pages: [ { - id: guid(), + id: 'profilesDeviceProfiles', name: 'device-profile.device-profiles', type: 'link', path: '/profiles/deviceProfiles', @@ -406,7 +408,7 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'profilesAssetProfiles', name: 'asset-profile.asset-profiles', type: 'link', path: '/profiles/assetProfiles', @@ -416,14 +418,14 @@ export class MenuService { ] }, { - id: guid(), + id: 'customers', name: 'customer.customers', type: 'link', path: '/customers', icon: 'supervisor_account' }, { - id: guid(), + id: 'ruleChains', name: 'rulechain.rulechains', type: 'link', path: '/ruleChains', @@ -433,21 +435,21 @@ export class MenuService { if (authState.edgesSupportEnabled) { sections.push( { - id: guid(), + id: 'edgeManagement', name: 'edge.management', type: 'toggle', path: '/edgeManagement', icon: 'settings_input_antenna', pages: [ { - id: guid(), + id: 'edgeManagementInstances', name: 'edge.instances', type: 'link', path: '/edgeManagement/instances', icon: 'router' }, { - id: guid(), + id: 'edgeManagementRuleChains', name: 'edge.rulechain-templates', type: 'link', path: '/edgeManagement/ruleChains', @@ -459,21 +461,21 @@ export class MenuService { } sections.push( { - id: guid(), + id: 'features', name: 'feature.advanced-features', type: 'toggle', path: '/features', icon: 'construction', pages: [ { - id: guid(), + id: 'featuresOtaUpdates', name: 'ota-update.ota-updates', type: 'link', path: '/features/otaUpdates', icon: 'memory' }, { - id: guid(), + id: 'featuresVc', name: 'version-control.version-control', type: 'link', path: '/features/vc', @@ -482,21 +484,21 @@ export class MenuService { ] }, { - id: guid(), + id: 'resources', name: 'admin.resources', type: 'toggle', path: '/resources', icon: 'folder', pages: [ { - id: guid(), + id: 'resourcesWidgetsBundles', name: 'widget.widget-library', type: 'link', path: '/resources/widgets-bundles', icon: 'now_widgets' }, { - id: guid(), + id: 'resourcesResourcesLibrary', name: 'resource.resources-library', type: 'link', path: '/resources/resources-library', @@ -506,7 +508,7 @@ export class MenuService { ] }, { - id: guid(), + id: 'notification', name: 'notification.notification-center', type: 'link', path: '/notification', @@ -514,28 +516,28 @@ export class MenuService { isMdiIcon: true, pages: [ { - id: guid(), + id: 'notificationInbox', name: 'notification.inbox', type: 'link', path: '/notification/inbox', icon: 'inbox' }, { - id: guid(), + id: 'notificationSent', name: 'notification.sent', type: 'link', path: '/notification/sent', icon: 'outbox' }, { - id: guid(), + id: 'notificationRecipients', name: 'notification.recipients', type: 'link', path: '/notification/recipients', icon: 'contacts' }, { - id: guid(), + id: 'notificationTemplates', name: 'notification.templates', type: 'link', path: '/notification/templates', @@ -543,7 +545,7 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'notificationRules', name: 'notification.rules', type: 'link', path: '/notification/rules', @@ -553,28 +555,28 @@ export class MenuService { ] }, { - id: guid(), + id: 'usage', name: 'api-usage.api-usage', type: 'link', path: '/usage', icon: 'insert_chart' }, { - id: guid(), + id: 'settings', name: 'admin.settings', type: 'link', path: '/settings', icon: 'settings', pages: [ { - id: guid(), + id: 'settingsHome', name: 'admin.home', type: 'link', path: '/settings/home', icon: 'settings_applications' }, { - id: guid(), + id: 'settingsNotifications', name: 'admin.notifications', type: 'link', path: '/settings/notifications', @@ -582,14 +584,14 @@ export class MenuService { isMdiIcon: true }, { - id: guid(), + id: 'settingsRepository', name: 'admin.repository', type: 'link', path: '/settings/repository', icon: 'manage_history' }, { - id: guid(), + id: 'settingsAutoCommit', name: 'admin.auto-commit', type: 'link', path: '/settings/auto-commit', @@ -598,14 +600,14 @@ export class MenuService { ] }, { - id: guid(), + id: 'securitySettings', name: 'security.security', type: 'toggle', path: '/security-settings', icon: 'security', pages: [ { - id: guid(), + id: 'securitySettingsAuditLogs', name: 'audit-log.audit-logs', type: 'link', path: '/security-settings/auditLogs', @@ -781,49 +783,49 @@ export class MenuService { const sections: Array = []; sections.push( { - id: guid(), + id: 'home', name: 'home.home', type: 'link', path: '/home', icon: 'home' }, { - id: guid(), + id: 'alarms', name: 'alarm.alarms', type: 'link', path: '/alarms', icon: 'notifications' }, { - id: guid(), + id: 'dashboards', name: 'dashboard.dashboards', type: 'link', path: '/dashboards', icon: 'dashboards' }, { - id: guid(), + id: 'entities', name: 'entity.entities', type: 'toggle', path: '/entities', icon: 'category', pages: [ { - id: guid(), + id: 'entitiesDevices', name: 'device.devices', type: 'link', path: '/entities/devices', icon: 'devices_other' }, { - id: guid(), + id: 'entitiesAssets', name: 'asset.assets', type: 'link', path: '/entities/assets', icon: 'domain' }, { - id: guid(), + id: 'entitiesEntityViews', name: 'entity-view.entity-views', type: 'link', path: '/entities/entityViews', @@ -835,7 +837,7 @@ export class MenuService { if (authState.edgesSupportEnabled) { sections.push( { - id: guid(), + id: 'edgeManagementInstances', name: 'edge.edge-instances', type: 'link', path: '/edgeManagement/instances', @@ -845,7 +847,7 @@ export class MenuService { } sections.push( { - id: guid(), + id: 'notification', name: 'notification.notification-center', type: 'link', path: '/notification', @@ -853,7 +855,7 @@ export class MenuService { isMdiIcon: true, pages: [ { - id: guid(), + id: 'notificationInbox', name: 'notification.inbox', type: 'link', path: '/notification/inbox', @@ -928,6 +930,19 @@ export class MenuService { return homeSections; } + private allMenuLinks(sections: Array): Array { + const result: Array = []; + for (const section of sections) { + if (section.type === 'link') { + result.push(section); + } + if (section.pages && section.pages.length) { + result.push(...this.allMenuLinks(section.pages)); + } + } + return result; + } + public menuSections(): Observable> { return this.menuSections$; } @@ -936,5 +951,20 @@ export class MenuService { return this.homeSections$; } -} + public availableMenuLinks(): Observable> { + return this.availableMenuLinks$; + } + public menuLinkById(id: string): Observable { + return this.availableMenuLinks$.pipe( + map((links) => links.find(link => link.id === id)) + ); + } + + public menuLinksByIds(ids: string[]): Observable> { + return this.availableMenuLinks$.pipe( + map((links) => links.filter(link => ids.includes(link.id))) + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index 53d60856dd..6e9e7f85fa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -217,6 +217,7 @@ export interface TbFlotKeySettings { removeFromLegend: boolean; showLines: boolean; fillLines: boolean; + fillLinesOpacity: number; showPoints: boolean; showPointShape: string; pointShapeFormatter: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 03e29769ff..08a8c92472 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -408,7 +408,7 @@ export class TbFlot { } } series.lines = { - fill: keySettings.fillLines === true + fill: keySettings.fillLines === true ? (keySettings.fillLinesOpacity || 0.4) : false }; if (this.settings.stack && !this.comparisonEnabled) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss index 427aa8bf3a..12b7bce17d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss @@ -17,23 +17,9 @@ @import "../../../../../../../scss/constants"; :host { - .tb-card-content { - width: 100%; - height: 100%; - } .tb-title { margin-top: 8px; - font-style: normal; - font-weight: 500; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.25px; - color: rgba(0, 0, 0, 0.54); - @media #{$mat-md-lg} { - font-size: 12px; - line-height: 16px; - } } .tb-info-icon { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts index be58092f1f..d9c2609dc7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts @@ -30,7 +30,7 @@ import { MediaBreakpoints } from '@shared/models/constants'; @Component({ selector: 'tb-configured-features', templateUrl: './configured-features.component.html', - styleUrls: ['./configured-features.component.scss'] + styleUrls: ['./home-page-widget.scss', './configured-features.component.scss'] }) export class ConfiguredFeaturesComponent extends PageComponent implements OnInit, OnDestroy { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html index 96c5642feb..1ccc2e8b24 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html @@ -26,20 +26,20 @@ edit
    - - - + + -
    -
    + - -
    +