From 83f42cd913b590e5186441aa03765115b49ef9c1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 11 Dec 2023 16:24:41 +0100 Subject: [PATCH 01/51] fixes invisible displaynames casing bug --- public/version_latest.txt | 2 +- src/views/tenant/standards/ListAppliedStandards.jsx | 2 +- version_latest.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/version_latest.txt b/public/version_latest.txt index 6ca6df113f09..2871567308a5 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -4.8.0 \ No newline at end of file +4.8.1 \ No newline at end of file diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index 26622283e4bd..6eaad8c760de 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -563,7 +563,7 @@ const ApplyNewStandard = () => { multi={true} values={template.templates.data?.map((t) => ({ value: t.GUID, - name: t.name || t.Displayname, + name: t.name || t.Displayname || t.displayName, }))} placeholder="Select a template" label={`Choose your ${template.name}`} diff --git a/version_latest.txt b/version_latest.txt index 6ca6df113f09..2871567308a5 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -4.8.0 \ No newline at end of file +4.8.1 \ No newline at end of file From 54be4b5933a415e3a3eb63176e85ae0540c76a11 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Dec 2023 13:52:20 +0100 Subject: [PATCH 02/51] darkmode redesign --- src/scss/_themes.scss | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss index c4aac8515247..dbd119303b6b 100644 --- a/src/scss/_themes.scss +++ b/src/scss/_themes.scss @@ -10,7 +10,7 @@ --cyberdrain-light-rgb: rgb(244 245 246); // Cultured --cyberdrain-light-striped: #d2d6da; // Light Gray --cyberdrain-darker: #20262a; // Charleston Green - --cyberdrain-dark: #2e363a; // Gunmetal + --cyberdrain-dark: #121212; // Gunmetal --cyberdrain-dark-rgb: rgb(46 54 58); // Gunmetal --cyberdrain-dark-striped: #48555b; // Charcoal --cyberdrain-accent-blue: #3e5c66; // Deep Space Sparkle @@ -400,7 +400,7 @@ [data-theme='impact'] { // Custom Ian Color - --cui-color-gray: rgb(46, 54, 58); + --cui-color-gray: #1e1e1e; // --cui-color-gray1: rgb(15,15,15); // --cui-color-gray2: rgb(10, 10, 10); --cui-color-header-bar: rgba(40, 40, 40, 0.8); @@ -409,11 +409,13 @@ --cui-bgcolor-table-header: rgba(105, 105, 105, 0.973); --cui-color-orange: #f77f00; --cui-color-table-border: rgba(146, 154, 158, 0.8); - --cui-color-card-shadow: rgba(207, 192, 192, 0.2); + --cui-color-card-shadow: rgba(0, 0, 0, 0.8); --text-medium-emphasis: rgba(255, 255, 255, 0.6); --cui-emphasis-color-rgb: rgba(255, 255, 255, 0.6); --cui-input-placeholder-color: rgba(255, 255, 255, 0.6); - + .form-control { + border: 1px solid #121212; + } //--cui-tertiary-bg: var(--cyberdrain-dark); // Core UI Impact theme variables. --cui-header-hover-color: var(--cyberdrain-light); @@ -423,9 +425,8 @@ --cui-body-color-rgb: var(--cyberdrain-light-rgb); --cui-body-bg-rgb: var(--cyberdrain-dark-rgb); --cui-btn-link-color: var(--cyberdrain-light); - --cui-card-bg: var(--cyberdrain-dark); --cui-card-border-color: var(--cyberdrain-accent-green); - --cui-card-color: var(--cyberdrain-light); + --cui-card-color: rgba(255, 255, 255, 0.87); --cui-options-card-border-color: var(--cyberdrain-accent-blue); --cui-dropdown-bg: var(--cyberdrain-dark); --cui-dropdown-color: var(--cyberdrain-light); @@ -433,9 +434,9 @@ --cui-dropdown-link-color: var(--cyberdrain-light); --cui-dropdown-link-hover-bg: var(--cyberdrain-accent-blue); --cui-dropdown-link-hover-color: var(--cyberdrain-light); - --cui-footer-bg: var(--cyberdrain-dark); + --cui-footer-bg: #242424; --cui-footer-color: var(--cyberdrain-light); - --cui-footer-border-color: var(--cyberdrain-accent-green); + --cui-footer-border-color: #121212; --cui-form-select-bg: var(--cyberdrain-dark); --cui-form-select-border-color: var(--cyberdrain-light); --cui-form-select-color: var(--cyberdrain-light); @@ -444,8 +445,8 @@ --cui-header-border-color: var(--cyberdrain-accent-green); --cui-header-color: var(--cyberdrain-light); --cui-header-divider-border-color: var(--cyberdrain-accent-green); - --cui-input-bg: var(--cyberdrain-dark); - --cui-input-border-colour: var(--cyberdrain-light); + --cui-input-bg: #2c2c2c; + --cui-input-border-colour: #121212; --cui-input-color: var(--cyberdrain-light); --cui-input-disabled-bg: var(--cyberdrain-darker); --cui-input-disabled-border-color: var(--cyberdrain-lighter); @@ -574,6 +575,18 @@ --cui-list-group-border-color: var(--cyberdrain-accent-blue); } + .card { + --cui-card-bg: rgba(40, 40, 40, 0.8); + } + .react-datepicker__header { + background-color: var(--cui-bgcolor-table-header); + } + .react-datepicker { + background-color: var(--cui-bgcolor-table-header); + } + .react-datepicker__time-list { + background-color: var(--cui-bgcolor-table-header); + } .table { --cui-table-bg: var(--cyberdrain-dark); --cui-table-color: var(--cyberdrain-light); From 8b2a263198c1c7b149d714e7ce2a6e70aa22bbfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 12 Dec 2023 23:20:13 +0100 Subject: [PATCH 03/51] Update enabled shared mailbox report --- .../reports/SharedMailboxEnabledAccount.jsx | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx b/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx index 3df832c973b1..b290c2ea9f3f 100644 --- a/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx +++ b/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx @@ -61,27 +61,20 @@ const columns = [ minWidth: '200px', }, { - selector: (row) => row['givenName'], - name: 'First Name', - sortable: true, - cell: (row) => CellTip(row['givenName']), - exportSelector: 'givenName', - minWidth: '200px', - }, - { - selector: (row) => row['surname'], - name: 'Surname', + name: 'Account Enabled', + selector: (row) => row['accountEnabled'], + cell: cellBooleanFormatter({ colourless: true }), sortable: true, - cell: (row) => CellTip(row['surname']), - exportSelector: 'surname', - minWidth: '200px', + exportSelector: 'accountEnabled', + minWidth: '50px', }, { - selector: (row) => row['accountEnabled'], - name: 'Account Enabled', + name: 'AD Synced', + selector: (row) => row['onPremisesSyncEnabled'], + cell: cellBooleanFormatter({ colourless: true }), sortable: true, - cell: (row) => CellTip(row['accountEnabled']), - exportSelector: 'accountEnabled', + exportSelector: 'onPremisesSyncEnabled', + minWidth: '75px', }, { name: 'Block sign-in', From dc806f54db2b4682b91bca7916e3036e47225d58 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 13 Dec 2023 13:00:32 +0100 Subject: [PATCH 04/51] logos darkmode --- ...One\342\224\254\302\253 Logo white@4x.png" | Bin 0 -> 72799 bytes public/img/data-report.svg | 194 ------------------ {src/assets/images => public/img}/datto.png | Bin .../images => public/img}/huntress_teal.png | Bin .../images => public/img}/netfriends.png | Bin .../img/netfriends_dark.png | Bin .../assets/images => public/img}/ninjaone.png | Bin public/img/ninjaone_dark.png | Bin 0 -> 14381 bytes {src/assets/images => public/img}/rewst.png | Bin public/img/rewst_dark.png | Bin 0 -> 14869 bytes src/components/layout/AppFooter.jsx | 46 +++-- 11 files changed, 27 insertions(+), 213 deletions(-) create mode 100644 "public/img/NinjaOne\342\224\254\302\253 Logo white@4x.png" delete mode 100644 public/img/data-report.svg rename {src/assets/images => public/img}/datto.png (100%) rename {src/assets/images => public/img}/huntress_teal.png (100%) rename {src/assets/images => public/img}/netfriends.png (100%) rename src/assets/images/RGB_Net_Friends_Logo_White_Color_Icon.png => public/img/netfriends_dark.png (100%) rename {src/assets/images => public/img}/ninjaone.png (100%) create mode 100644 public/img/ninjaone_dark.png rename {src/assets/images => public/img}/rewst.png (100%) create mode 100644 public/img/rewst_dark.png diff --git "a/public/img/NinjaOne\342\224\254\302\253 Logo white@4x.png" "b/public/img/NinjaOne\342\224\254\302\253 Logo white@4x.png" new file mode 100644 index 0000000000000000000000000000000000000000..8654c2d794841866d183ddf18ebba5eab4cdb845 GIT binary patch literal 72799 zcmY&g2|Sef|9>16(Fm2ZglNZV3?bwy9d0s&GE8aQXBzi09ZRjN&6JxC=Qtze9w|0w zrEy;+lp$B{>;HXbw12$ThTJm2GUzCXwJ*?nMacyZre!MzX!?bFddX97Vy84$!W zz_uGaSrl>m3jSdC(6;n~AZcCrA0{9FTLutx6w*0&#?&`ya^Q=naM$hpFFt0g(eI%V zHlh%do%m6A*0@N@zeEq#uaQJ!)~^>d(adMK*&--6SZg#8M~zEe9*7Y;PR^^EVQ+Y1 zONO|n9}Rq;Ukuq85!qscARJfjbUrQnPjL2dU8!fP|7pT5@cZ`&yzOqs1^%+En@KfW z@G6O~Ubn7gydTH!S>Vrg z$2X=BM9J?_ykpwvWe=Wcn$a49CYo8Zcg*T$q0d5DnDIsa@%ZRL@Ji34_qDlNsv1nd z?{Ops4PF)Apk0WVc&?JbAkKhbncSKT)AD;5X~hAlTRbO>Je*$X7XXiE!d*wK7kqzm z{+lBEXU6~vigW#yz6GfIz&bY-*9L^O{|{mFZ1AjhSL*#sSksF!W%S)LJX2S%&mnf~ z7H0_4?~9^M6se@1W&v-4UshK#g#3FNaddwMGX$mdW>jQZ>kq>0FO^36&dt*K{XMHt zmChrkXAo<@E&QAdN-IEiRXf#wNRNJ?e$E7*V0pMb8Eg!h#pJogh|F_q`F?iOx zK%&#tZ7J1u|7Rn=XHndEVJ>UlHUoVp(rtlk1_b*L-9&!RQr!#dXgIq2ax8-?x!eFY zlfwqs9kfFM7hoQjeJ)3`)F4*Co5BM$Ad0~Mv59UQy1X>5p1N+@N4HRmPy=wDq7U8n zPY=JEmCeum^|He6>(=4z<2&d|T{09pL!R%^0uP7V7Ju^4<<@PJ*dAP4Q~5JbaQftW zak_?#|CUgQcL&>*fy(O2&4Ddp#vV99x{+b9rR%YZK$J?HKofL>JpfPGyw}liGNt{6 z3GK)o3~h0%yxqO}?yST#KJdHf6~bE2^z?|C$aFC5xM0j2y9Zor*u&N9Up^rFQQ%WP zT_3-lfZwf`whk05&1NqQl#mTGq;$${kL&i1+3cA$p<+x3w=zBoK z5K&mmVb=zL#eaJ!1D-Y)I`-$JF#N*9<8tK8=keKWvs#4kOPjXMA6@N14y@$-fcuvR z`Zta+Hc>K$l~Op=NyRf1C!E+&J>h=%4ovCrEC94t``RV_&=BuHpN-CtQ-i0To0Ua+ zP&cL-R!LMl4imBF7^77KR!BFOdKUPr(ltu4?lsMfT>k7h$T#env@kQ2ZthbXOm_R3 z-&biT_|hG*aP6`hYqszlpa#d@sn(x!gkhtFeTo|@=SHicCllX(`d_N9 zSS#jIJ4gO`*NkyK1pzxdaiGLWc!ogNYPyAaER}(6&}qkk0xi?yyk!(yB{$VS*Vp~N z7^%G_=jZf|9|0%9V_h9Kux>RP<7`XC$N*|_`APVdcT+|f8&y&y{VO`@7wvm**byWLL1(8W=$~_o03WK2=p1K<(h{%&bNilo zz(L1xqv_3u)8|JdPJu;nt^-OpeptggP`W{Tn>B1aaFJY-6HLzM$=)W`2F5u`hvg~6 zvKs?h@H>pKBbWCDbMRCech-LkPQz|HvkZVyDl8BLfzUtX z=#v*2zDn_0vs(m2bc-iWRIJKgg2Cp=wZ7NpL9u_p#Ccd;ji$%P$ML>Mqk0r9^1g67yX*yFk>#n$)Z@)lAg zC#PZ0Bx5zsJ?L}*_&J$nM);`o{dXwmS|amNw;{Cs$E#WWZ}^mO$cUDuQd_Amf7Z7^ zNCdeu5dIS29tVteucwg9l)MQ#0jp0RZ1U`@Ol z_<6_;gEiL}fBGNJ)-+ec67=BjjEvS9xU6wZcb9b6rIGTf=twlR^h&Gnc6JpKNAC$c~QsGQ=`_ut{qd@ZY81NA4&2&z5^3ZHWJpnM9=+AQ;ABjbEZ?6NnkHH(L4 zAmuu5Z3pZOCvZ7(?X&AoGoV;v{jmKKrAC?DHl3eJ?us;qRSAal6ZlwF;kjyuDtcO6 zoEM-(+%`kj%t(9D@!~K@^;yh>fw&<-B&#C%^)@aAcbqB)KPl&E4b9k)0_#ya&+{N> zA#02?%$T!}VL`(l!U`!&Tm8P0Rq@K(DePuEd~lcprrNctdo=wrpydg!sEO-&D54Q@l6nZp9`VP4+ zRB$hBq{w!$?~`GfgoppH1#e{<|{V+OWRcvtDBu&gVS6CQXC z=5TbI!$)Q#mj5csN)g9zcCwjP7odcm!Nv)Oul!I89PC3rmCinB^a>_IXcyZ=N3%<1QYcN+qT(a&4!*B{TANE*;cs5nGs34Y));b z+RR^vC(q}3F*KIqO1N6xG{2vS^#G=suZV%q1fwtpR5?klbMPvRhTh{{AU4a+7esPp)m z<$>Syg}CD|GNb#*6)hZj;E8l2*+!&Bm0|3|0U%hP@yLiBkqWX$@!vHMBSfq{d5r7OETpbyjn@l~*6{H%5Xxn%1R zm|k>UDR;U`%4rtjUjU!XriDVx``d&W?nAMn+ZSapAch`AnqOuIYm;CXQBnnQX4VT( z3T-AAPmEu9>!I-51B&U1J%em$vVcG^tJcdEB5M3JBV2VIhbvw%aJ%8mYI_hjZdneP zj)O$0FfodJDza8j(JPbjZYBvfir-_gIin)aTH(1EG^1ch_Mw`UB%EgW4{`h`oUCq- zLXl1?LHx)~RbcUJ->bOQtK#U41NS&CxpUqcDkpKm1*E2neU}!nBhyJ8@2@iU@D+ot z>i4L0#td985PZ2G@94+5QONm5xV_0mY)8B z*E9Eba^l^5UYr?@OHh^yIXEjnXW_F1V-H4Ew!I&5pwR7{Mve11O$evcMozq-$xVMl zJp4aw1jd5M{(TQ5O}!-k)EAR%I0&m2cycN|Q=qj&czaD`K!>tvV!!toBp^uqH*#CbI>E;aXSziUeJs^*dx!^E+^LHt} zd35J)cnhZ=gI2I+g4V9gmBICwW>v{MBsL+2N4 z#haL*u!*eIeD16(46g#4$^6f>_6qKyWj9doT9Aq-GW?anw)C-+uJ%#9c!j4TcqRSe z(t;MjEN{$^AuA=9o%4*GsrOyhKiqeNpshha@o&B=+q+So?PS1LB@@_h5feyj8i6q0 zO^Py(4Ab6WYr~FK^3Mg21NBg%0R6sJ2K06YNL!3|(to6j?B93a5rS;Sb1U)`>Fo@# zn=oRhni{>X44BH;aWN6qZkkr-ppH*74pd^>NqzLl^VOFsDWIRQ_&H~y;>zy>3%hn6 zSkp%KzZ!H3FeXeao+#~Y+3w{O*6y6OS=9lADGSef_+kP}fPcY*qP|lQ%U-2`|5;!R z9$7UxgO_3BrH{o2a3-=nD6y7i=G4UZry-*MC9HYoZfM zG~W#7)5qeSoqLHCXS)f`azHrCD6$Coze(OHJ10$7W(_Jl=YuFWtI0d4@_xK?Qi!)1 z5CbbT6IKlzyyN$A-j8-pa=&VJin;<6OyN82_%_?xa9fFmUjHL4sw1KA{uKzS{E@Z# ztRil^y@!k4iM8v!r4?>(r9s`fge53|gW*BRr#HOlzd^>}VOgyM5PY%KkATSo`ldg( zP}wP`FJyms@yrcFGHv44wp$a=(>qGr+aLzW71?cY`zj?#iJ=F|^v-h-J&xjro^k*k zW&(j2bEbZ~bNEg`5|#q-L&?RR+}tD1*!c;)+q8X((z(71qzMP$dAK5TDs_?peUKi# z=G>kmoZz}333kCL73|Q%6F3R`J(P}_M}qK08Mij=da#XPJH@tVgshUP#td2H##~5p zQe(8NAd1xv%cs;_RS@HSZVFMRm9yQ*9lx#6j(Z2{EcOJE=&=OnBdoBwanz6zkA#oy z5H8MU;9|C+B=9cp_MjxE)4va!-znY2kB$U5;LeO6Hgo66WZ)X2r3}v8e-G87j)@BH z7*M2NasL@youJH^HC!He5-!BH2SuDGof%4bljwB%w>fz2RMX;L@G+!8(*pZw#u8tr zoouUjgUSa|t**UnUVM=;K~-WWJ6*0hJ`X@Qr+q}#lM&Bx(L19=rOVaocpzIPrLT-= zN*Ubg-riJ2Sa(N$ua)_qwGN4ZI&|u`fff4fa0nu{*Z`QZ1>*P0f}I+zyaBH)RQ>PF zojiNLqn}y+mXTKwtLcfuWVjKvm2P1(@k`Vx`mX@SHi5slXJli}X!NefVw2;@_#NP-!|Ol0rdqZQHiTZT73Ks_h|)VidNV-;%Yil+77Kk0 zg=U9dz|c_h)^EgbcJy~2+J@fD?pBx##LfnR-YdhlOc$PkKDpuPiqUi~mrrC5(bHg(SAcmAq@@(;0SevO; zj#V5B-XN62gE%x#uLhvh72^$~61RKV=U~CdO|4DHP+`vnS#651eZo z3xcCzoGfIW&e?Kf&CUnK7MQzgx>0-Kq0`j?dXDO$2{t1J`Fvhpiuz5Z!%!Ps6A8bX z&H<%9J#rd@<|r(WjlKXz&Mr_g}#c+Ah#FvNDQ}k2}9hij@TB6sB zo@UAIM*wyDQ6RaK6w!tG0$l@oF!n`>__LY##qR~gN%#)EHUgax5XmUFEA@eaDybf) z5`NncGu^t4rnE-R&*jkVWtea1)K5$SJt=2^$sW3WLp{asP+2-^xJkxhM@_r#8n^NnK8R*B-mp~m1m@14>H{I&+ zbzGN&4FMVpu-Wh-B)~jU9#!WV!s4(fq%{AlJ37WGGvgm9dC&&i}WiuisACm1Oc%OfU=M?RWR(PwK;>ynTw!l2^*dF zTb+f{+3o8(qWuy>@Xv#6Hq!m$>4KjR%G$h~mlIAmVefYt{&cxVDLV(IG5l$`htUWt zR091PpRPuFHSzSq!wI=P9)JVykB-1G*dXFz!r=1LVd}QCQ=ZY|$j@>Vym;==5rahAHEmf07#`6QE7o@TtGOZ%QfI$lCi|*2k2wM zCUL~~sw){zjOg-XVcaT!4q7~;rC*=fP?yl$5IY2@?{+Q7DTc+c`1aDsT}0x<$aY0S zIkQafEdt(vBxWCdVw5WAJEkEyopb;PE?AhisRAFEq70H4`e`6FfDJ=96x@V_yl3=> zzi2IJfCdP@W(EPYAY+)*1?y`vn=jISg0aYGf?ll>k1sr%{j_AZt*&Wsr)EyiTxVD> zQ~M?y0Jsp<7ReQk>y;ezg~K{<0H6e~epr?pWXN?Ud%Ck??^(@!1EoJ~zDnrhRN6M8ftC-$5>pqmXQ`E4%EN$mid^9LFV+i; zSfUz37_kPdjr&*+9NX>;smj3tw_Ass^{l^o% z9Xg3%IAX&8iy-bB0k#PW-?H(*X5H3gg-URd1QbnkB_wbiW@Hc1=is#I$#l@jUq8#} zWgi6q$d_*n8OAw)8^^fU`C9a@wMupe1#}qlwH?_ z=N;w{xDaqjhhA7&5dJ57{AMqkS>AO1S{${4o5bYH8RD$k$iSXuDcA{A6~L}?bOAYD zGU@sKEr4pC!5dSX3CzOk&s8OQxjvsKc!?bI{xor5KAStf!co<}!maiV#>P3w$JPJ- zDptuAaceGt+B=1&cJ%yp5I%& zp-cXngLs@2a?f7(N4eMYfss-QPQ+B@U|BunQ%%lhxrw*ja9VvzZe|m@SHApct}-MG zJ{mquj=s+;WiBOvL#IA=1CG%LT*nUU6H<9D;OSd=B_CfY_U5LXQOmezJdnX^VtFbd z?7X6=nd_sDgm=)Mh3R8mK!)_ua2^J=FvUiu@t~&2y0VYC>|jHA*D(QoYP%Uj3%DDH zbkr|euj$nfOP*)hj zPuZp{j46{2?js-Vj?|foU=RX>jGeeE_2;fmi4o0tFT8VB;5ONXnMLAL8Hm-Qm2ZeG!YbZqdfsK+`oh@d#9lDL^;mKwAE&$s7pND7b(2FWc6XQGxjg5)nTm6&e4nh2hl6ac~{H2Y@L}J#BntJP{I`3zV zi;kY|mRy-8O^?~W^C#Mz4(EVwUvtKsMQ`07z5(-{bpd^_;GxU1APO87xz%15QkiwI2+MURuR!Iq~t(xm6DDBnZ;RlId%3H>g9yRO2 z0(dG{XFB;j@-J%$SFtK|zKpYOgdcFRkT3YU71?14cknq}kWLt2zL4y;}=?Q08NLf4&5r>&9bShRWai-bFsw>PVA6vvC};$lTs>gIAki zR>A|CA8L{-=89faLhJSA?0MD0Aqi#7(2&=oqJ|A~#XY~6Leq5EKd^+J$0=hxV!z3a zR<6t9r+E28i}_iY2?S3|Bzz?26P#4ai)OoLOSw*pMPWgrpwwQl2!{DY+ zog8Jpgd3D|5kres1OcF7XAFY6AuwVD4de4oItzcAG#r4m3jcnJ<^Nl~a6JT^*HL7+ zVt5KU3wW6%T*y2VgvZ?bz18V-Kgb01t_%QFNP# z&Y)=?D}T6R;&`Yg=$_}cm^Hr8>(N*de}A5;3lU#dY71Zj4FznW&!66{?+$&+mF9Fk z=)Pls_Da+#oz3_n#l4?P$XE!?5$=K-^Ai~-!Cc~IHivq5p_MjTS-Lz~jzDO&)JOft z%JZ;nL)3Kn!w!m2N{)e_o02pO6w#AOl5GV(x%}7W*=SlEkm;Jwlbep+a(EHjgJ%v< zW~EJ1)HKzWkR!xCeipP07jpV(;&*}Dy=^AJcbft5^NuF*K+uQ4jrD*1n)<(_ciL!F zZjOUyD_h2M`)7PmTv0SKOzG#ulR6*%Fy_kd&yfNUzlImp)1inxP0oE6gqV_lzvjYB zb`witR;n^<@p<}0{h6Rd5r-wdh&~)u@)FFQ+qNAUFxxp=ga80ex z=jxeeZery{S)dWSe3c^xO>0iuHN7`+#?q1T^nGxgR@RRc+VaHSzn~YlQsy{6!uRa;9f2E3 znUmZlHe2QD<>TecW9`!`Bm7l88hx1E2hwoy<3wD+RjXt<9Va$osR4$+mNkY2MeH)=Hr2@O5-sgOe4AlyV;n62t zt=x}$SWzG{eI4XSQId%ed6Aa7I+*cgk~VJe$j4@@$2MiRSx{8743wpfrryG4$SYWU z-vwoi+3I@r0Fw^9$oou=(yZW_a{@vKkC8_{q^(ciqI0ieXd-Aj^zQWm-xPd*+`h11 zaYgPmWUD>;J=i9uD%5E8*e7|Vh)pqs=)al;;XX3JetN|K1&U|0#=aku5V?L@{Htu@ zQ*!0&11v;w9(@Hhtn@EAqt-5?g4Dnu+Bz+y)ID{6DY7f9U~Bzi2p45ut-*Cj%RA+h z#h<3TeIH0NXU7#R@`a?X2c=ZX&;qwSCI(K0Y*qWAAfp8@s+sV*NQEMP>uonLPN}9W zwkvpi$oXbm`K9Y2(T)|Xt1EGGhSfPBdT1Lo)g1BQ-!kmkDyl&=zSO;$IOi5%UUncZ zt%J0bFf0DyowCGO_WKUCzumWLx{iF`+8prN>`^gVa@UL>d!Ja4 z8hqlH9!TL*Uql}yvaEz`wr{OylsMxwDmd+*EnyI4Sq)XcL{0i4fArOv*cz>Fyjti2 zYOu0Dxm?RZk*~6&_@&AWLSF5W z`Tjq8CZ}CRetjQ5M{}5aQ8n)ztyUVOD^NMuiDKoC=p%&^aJA~`_q>%jITz%S&ZSy{ z(euX7$x*KCOPG=r_yB7+5_FnQnxpjH% z5B{h%mpkO2&%<^R)wuN)+`>tkWE!XQuwz~9_ynK(nnqt7hY+gpR$bwX~tW)tT%mze(fTO50z^#N8QF ze$teeC4N0;tU&PjzGPUz=|1Sx!XBX$V}SYQtv&rACC}E*P2SZ5&8D(E1pZ9^GK7l?nKWDs6mOeqC6@_$F>8Gm>2)ZM7vHC>Z;x0l|G3!L?-2M?E^6qLt#0uT zxkWo1>WR)E6~!$1RULo$Yd#38CtvAY7T8Qth^Vt|TK%VFZ(NEbzRlOHEW+ldp<_+2 zYPG`$<#hj5FI^u$L5p1wX;xPRd;sqxF{2vPP8sOZ$XkJCwC-d#JbSNn$Uopv;P^9l z(`i2M!^4Nera~7hEm(I)ja+1D+r0eKOxU0Qx#Wo8-9=W+nILQfw>FiR2OVUf=+&i| z5YzBgJtVcZcZPW}y*2u7w(tj$XvvXyn5hy4SsB5cHJ7n-8|(M4RW>W#$u14j@5w38 zxw_j!)1WEf?V5=7^bnH9-I;Subye^7F8MdKApIZgOc~GK`!4qKjFZNmw{}2MFYT^`E=fFXOLD4wf?`>ewG#l_Gm)QDiF%Nj|ptpa8S zD)T&js;V=nNxd6nWkho&qaS8Bwf9>$`k#uhNt7!k7(VDtP@S5UGfa$XR2^va5EX?m zH{<2J%UhP)3@Bbf?VIr=^AM)c%NZe9m)Qw-$mqCb@;8T!wai$ zrF%QntVDM?9);&rS&YQinR-|S9iYeQ2!UWP#x7mQ)qOLY&W}$2pYp3FTT=-&$Rx?%}%@oo4nQkY}zeIyvSuf4r z3?jN~oVsG3i%0#Gt6m6i1lclZ7%*8+sJvh6Jx|fzNV)l4TS;8MOAH@zI=$Lm=}9AL zlY3f+zZ5l6yN*xv(yi}G#Y`;8XUq+ZCKKgPio5AMw6rJG{hy-Uc0mWN z$7CfwyPB0FhJ8_(;Q7J$89Dz~xYrf>e?BCSCw)RM@FBckrq*r#isfDmvW7fB3bFcu zkZ~$B)b>ZfFZ0JVXjqdKL;Z+PpxZ{h=(AYc-WtypMYpre{Ermz|NJr-Rf#&&!8_UI z*EDbIR0s9lW98I}X?;dht}4iCKXC%8|CEu7HzM>qwy>`^i6tukL$r=c*XVxv_rqsF z70s{iX8Wg_bbg(lc*3;)*5FZDNjUMJl}APD*IZcVJJMRB?;$D8>pqhuZ;XFAw>0Z7 z_1_0Fep#I=OPv;~pN(r3NbhSZfzT(gO}-a~R3?oyyuEr3Qr@y{$y-seLs(}QH-3=)bsa=757Y;K6eZ$o?7tC zls!J9W}q10j|oA@1)e1@j-%#R4Tdsv#q$BJfiHCC%%{X~H#U=tE- ziT;O>Bi%Iptw}&9K{@m7ezt7p;gYsuWqZ6nOBi)&yx?n~GUYpahDw$4iC>G-%STer zTZiUzWnTflfJ0TZi0@wr0NSU$if165rvmqdcZKZP%t}$H`2-lFt0l+Iai;oxr1j&i zot>-KNLuST4pM)KzHU}cJzp5!NSZBY$@uf7bp8EVHiKlTmiAPH^Z7(tQ zyGcXas{GTaXryz#59oU0%YeE?pdh`}Ix6XxK~qnBH$-SX3th;6 zA1Ji1<|6uFm4578V*LF2S#8QS=^{rHe@yuy-xH$EP(jDJm}uGKEk?Z8O!f6bvPL8q z7lKDo^N5VzMbN+S1l3hsa=Yv%aeO(h$ok7L%go7~W9GmbxtA~AyHfv50ZEhg3YJmM zyrndbToZ_m_Yo28n2`V;Kei6d--<6ijL@-JIPZ>l6c8Z6~#wDi5ZasS7v z8eYuvYB_=^>xgOC(~xQe#5(>%>>>I1FqVaVRK zuD8nRDPFp3CE5bxx41o8Zv{n!voVc57wNNVTw&u+9K!g|PfK+%QH<8jrR6^N z|9L2&r(m8m-Km1dT%DJ~EK0t2M-DCFn~Dz-rz`a>T~@>!QYp>SCJk4(kY1dLsL5tA zytsRE>zd9V2Qh?%Z$Xc?(sKJ=fijjqOA$Y~YTt78wx5HoQM6yV_DX&MrZeG?Lin`< zD3i_z^-TGDEMdjDSctRV_R-b@W2s^MWn_C)V-tb$FgfEFC$!d)FN_>RwC4XRduy;S7{@-<7vXLbmty3G@|k%xlE^qQ15y+9Y&b2Vv> zbH$__!7n$j+8wQ!7_K)}_EKi3^49*bwA%X~$wjFjO>=h~B z#E-5smFWU)$Tg7M>4UsjZaVBW0eaNRG*qcH%+b>29oG`(Ai2jn;v?#bcXx8*j&&P9?v$iyd%|T$^p*t+t zZFk^MBT2SN3#K(dTlv@WOK(xM@o}V@6vYiC{_yV&8jFyX3fnuKrr-vfm#IwX%dSl) z*W15e3(Njw1fezWWTzB>cI9G0Yla_IP9|{Z{ILP%dC(z^i)h6SOC-Ezy0_sxCvV(S z$-epX7=Cgw^);3&u4d{e-k^o@*;QjcIJ`N0*dAr#FQwl2?&NxcpZsJyVR9EGdi_tl zl^AJncP#3?hBF@%3uyA5_RU5mme`Mzz(Z!CVe{3E2*RX26mf=ceC?XsYVaMxFJ8#> zc-%x+1u6Ck)1Paf@$=Q6su1nsKMuv+-Sif_CPzUfxTJ^*>UPH}4jL4OZ+chv!2~^# zOuR}Mde;rbl};tT{j0IQISKDIt8ZSy)9^-B(7Q~VJ%f4krwCH*0<`8GGZ*2Pi)G{d zP~^>o=D}Ja8iN;TEHu*e<9YWP)y$qrd+CuR9?FMXvNgBMPruJd#T{fagUr}6R$qb+ zSWm&G;%L~ib)B=z@d1>r7?Zx1P#G^1`2H2)!k%_LwZg^i$!ztVfoh$V!vpW?uHgE6 z`MsFuKfb$ic(Qr%c2vHTM8vyA)ry=e>=BIy_jcEp9f2F%OuL9$mg%{Pdo%B3=9@4N z-L9pz$K;NvvafZd0bU!6iA*`U+V}X^fV93 z5dO&=5(py>uO+a3y5~NSnOheO13FhKCPmo>Ac>Y2rV`?ho6;ovfaGn%!KBT29oTvj z=1$O$c4VIT)4fkaQEf{swUSTw4*$!FIp}s|cz=S$_rGj^9Or4aSl$&n-KXW&kV4Tb z>AA>MYz_PZKzxPK`NxsCROc_w7R_HG!z5Y*9iqd3(* zl|QOI>3-}47t_XZH?_a4d4qHcK{$gNVE^*-*Y?{T1lmTN?*U!Lmtu;7XHzXg>_U1; z3!gP?)u5;!eMBi}F~@*?qcdlBRYRYWIl{x(YhQ+w$szx)mmiup{{+^oc zB@CEA8TM{hdpdLs~b!@MYy0SpK1K0rGySZOMtfc=X^JHN9V2{1$U) zx`t_GM1419HmNa%9WCNIIbQb=`!4-tRlk{T@hbLFb45Dkqk?S4?ZHM;HPO7aKTO#HoMh6Ud%9m_@4=KHWjHx*PIfGwnJt5>g0Euu zFFpr3YAKs1=HzEaa_wX(f4H`v984E}dpF1d!_2$Oq|Vo6=<}z`K+*}Q(H=2vfw;Sp zi5VB#RrzB(NDrU&b$5*5R~tTskxDwxQ=NuA629NPd+v{e=s@fva6`wu8)sPwDG?PD zHKl+W^@cRQuM0&qpKqF8lerHC$Y zaq4SXj=%|)-o3#Z3{^Uq98&6%ZO7HN6B=z*lP+>-n+A1CF1e+%%UoaA_=69w(o1k@dBKDL#GWi|EYj-5px#y0ss2A#A% zxx~#gxTL|1^{7g4UBTIePx=Q6wOz9+*StjJOSpoj;#o~R`CXfc^>v5iLZ|G$fy$0z zp@0k%*yIgQqqrrgs{KJzep4fDQgbz=p_fhnFJNH+&;{d$8Y>jhK^s=uM-S@j)n+a! zFaGi^k0JTIu|ZDXFSU~`JZ=y{7#8vTmzO`*>>j2LIN*#fRxy_HS~x0*rxWV9`DiZx zwO+Z!0bKrS^W%{46a}0`X@D{6&n{-P6ox%x!sO6MaJW|NqDUGCxL9?Zx36>SGx(0D;Q!1#c2+5c-1E*9K^rXFFZYp z-j{Q1D;Jp7P3|)toPCSR664xR_fT&HaZ!db>rXz7J02E{dp=JZRM-%r#8rpueS(6W z^y^HA0CIV}uTk)P%J)a&P-=`-7m78jA<~?pev5lQv22I8L#vFhv zE=Ng@_Ebe_N`W?N;qIru{PIV5&f-*7z05-{Obep-ZAdt-&N0>4a@LuGAjdEKeOGDn zF~8Iz_RrwUi zS>b87#vQ3RR@;SQj+zuh{JL~8@#g(OWeG%ft*@~K%gmn}68CbHGFc<)@w)C4iA@AZ z7&8?1l}8k}7%7y`y;IoOD&B-~K`yd*qs*DI%$Qs%E4uYLD- zhxs7eN#}uOA40ajSn_mZ#`Wnm70;qpS&8x4-1^`Oev3Wrt3`$O*sjI)FV_5}^Dbv^ z3^rW<1Bffp{=FGhH!gghw31_t$E6yYym7ny@inW~r*jYGG))lL{Fo~6y%yTwIIn-MlhvSs;@sBhxZ3m#q z=JSePEJVT8BMAGKOu>_4cq3YowGJ>i}S)3Jt_mS&v@KfmhpO-l}kfdY(TJ_UUFdS=*oRqxaj(9;_`&$Wlr zoRp?|()?pMa|=cGPId+G#N{@OqQgKwr*OSl)5%Q0d<&=&#@59jt=t|cK^lmMoC6-w z;r^{UI#P9brp;uK;O209#2FJD^2Ufs@svw%BTG~^7tIxXf$%mt{ny1j%woR$)1sXD z?)e+)n*w}qm|Q9i=AuC#^`BcM9Jp79kk<~=NWntmB&8?Kq?0UTlN8x1y`HYZHcsIy zLO^ET)YKC`p7LnNu)P@*eEuS05C0yyD!Fp9#)smWoUVVg7qkqLTu$+U0t$T8!>gU{ zc+ZxH%=!7l(1)9t%KdQ%!Iei{iH zD%SP8@aA*!1*L;B#RDf-QJwM1`H9^_P}H>8q5`qLLg$CG*AWh!PbRYb5bO!UEj4$u zrGJ@ph>fCGD#3$!!lNSo7di(JIqy4UGj|!|%&|?y=d>XOtBYB3#$A1{&A1Twe_Ac( zwR9n7NEWPs(C4m+QN)1i6?dE_bDo6kWHmSidIw{+A& z5*Vdfd!qegT1!qBD6dAGP0n5$1Yj6f(9zumEfzeGMNn$0!v_O#&leBsQ`3&U$Om4j z(uR>di-=*-no-BId}d`{%+XnK-8g7#I83N;Im>*H+0!NbSQ*mx0K zYJxn!!z)|~ee5`LL?O&ZIU|Rpmfv^Gb-;xGb-p)~2kX+TG^=k^GwG;CTv0JU-;Aw+ zqS_1dmklkm*oF6P{pCkPGnV zphky#MUajkp-PK1`3Je6TT)R$s<(5CWeWX%EdAN1-TYPhiJxZd3s{GzpLo9HL?g^S z@84AcJ+9m?q;pu!-D9Jb!)P{)Px7Zd5M@3v>cf$YX{ph`y&y0vgft8aEl8Vmatu*X ztiyW-)2d_=LDDSVVUzEYw9i9z`i01@xcjlYF(sb=-nnM+c3(3^DJf05-wJ5H;rc(H z?pGK(Ht-EHO*)-^IDr-Z_B0>gk79|aUn3p$9UezvIs-Pf9hX^fw*SmU+`_T&$87Eo zv(t1^YL$HIBrsH8&MVmVdE<1a2$Q#G;x`-6=43f}D5Xp>vHDbOoC!vx8^^`ZUr|i* zdOTjj!F1tSc=KZ>CqLd1MI3XAkUq6Rlp}ORHC?p*92ct@xRfeN1xGn1mT7 z?~ljIA81O2*6s7B@MGWIc3J)hg-=?!D*uip&?s>(=~o16!*}Viv!8+2M7=g-cXgEN zJbQTjBgeZ}L4Q-&iCq?wE7jiI9-7!G=B(%8V5b+gN&of>gOf<(m%;b6xZ%s5<#L*? znX+yy7k--8nf8?hU;U0}s~}CMJ=l-FAo6wW;_~H~xyofD)_+0cH%--OQ+G}=<@o2W zw}2*J?AzNe-xtm_aUq>g-yt#8xW^Y|x~L-};)NOn?!=Hjd{A(N>vOhBu;F_!DDDOOD`E7o6`QVmrhjNs5X%prXL-5^z&1#}l>tFnLa!A9Cm29`-E6z`PqFrN=&xW9? zHmwMbc*SdZ9bI@{Q0eoBUVr>8Xi5yS{-KqGVOH!Vd6ubr6TkQg#a?6)|K&O4_JCy+Wi#kGTld#5i=)U zn+A)*qlnqEeAgXNCMZ6u%v=pQHK49x;^yNy>s>ev_2Vn{Q)0uKZP!ui=fg=HO(I{D zc><&BU(bzr8D3TL;Wl5o^vJL9Qv)6$NJ<% z!d3Ta5{&oxMk*!}Pq*`ij;V}d?N4&WJ=)YP6?z?Cp*UK(X`g$MMK#Rs0&W82FXu9m zv}i75nE#Z5Q6m9Rq^X{+S^#wyB+~Oh#M^FPl~qkLp=sA>=Ockf>LlGQQM7`&j+^Zi z|Ckd)XueN21O4E-XMClA^nPrOE$zDR>bt>W82bMPq5k?|P+kT93bR(Yhd!6n-NKSV zI|lmYw~NsV*rrjohIpvG`2GqPTI5YOKRJe^RZVLm6oMYf3ps;mZf`CRZR{iXGLgyZ zywQ{?j4Rhvj8=kGGFkPhi2$O!inV`)cnOG8UMHpfLivDry_~)UK0RC~zO%-m+Ds0G z$iF&?7vuo{j|`nAo18W3LJm38K0xi@!%o@6EyxSh38KY&PVIx0 zqlt=9{fuJ9y*VNVJ^V4@#7D7?E0#Mqj&u(CKepZk9Ln~49KUT58f_S5X|W~Cpk$}l zo3e#$$7ww8%QL7DBuds{gsi`}zDn-|PBM*EP+U z=eh55pZ%Qsocno}PJfpQ5o%Ly@ayuwe|e#Se>OLm00V_W!K~hqhk{AT?MIJtBJ$Q< zO~_X~C2E_h>33~clNH3sn1zeYL#Nzz>xMnL1Cp?#gWEqYDjJVQHQ_N}x;UnH^(BUn{tc;`^OrtnAzQX|5`5&lA(B>q{A-mM>pE_7NI)IYm^2n7e zY!XRPo4+_Ku{R}01R%m^wIRo*6mB%^%pbWEotpbjgskhWd8b)-YqRbuhu6yF?7MkT z-6`ZS?u%w5Q@FF;mw7?;V(9pXcXDiuZPk{%djzAOqDy$V+s7eyogoDSW*@&cs4^s} z0`<8E(qc*8sL(%cuq0qjk$akq=X&+z%!2$0z&Czg#W z8q-5muQ9G&?f0;Qr^p0a@aUA(9N8&wd^=V{|7&~kKHJPt3)ncit$w+yHvHo{(du_+ z48gnMB82QDu%0Y}7`)#SRUVQE1`S29sF?(-baoLwZ|hko3N1-&=e8t8byh%La9(sB zCnB8GOoh@@EkU1LJa555_e+to-MdMFUYG*ZpHe4%QkIB)sLe%T2b>6H|CO}8D9M6k zpJaoKex1E>qml$efT$34E{ve58NCG_B&S+|r@9p5EkjOx7Wu2>qzHPKxmPTAKZXCX z-x@=XDd7-RD+_3yAU;6=K6OUr8KPEIh=;xG=452`{tcj>Oauo`Yaw9}YDdx1;ZlRp zLI?KoHz4waxh;mumn-M2&(X|C5#p~{ZL{lCnPa0sGlm2d`{~Orup;3^GZ`YWc$xzZ zwAc2E(2v_o*g`l>zAP#6j+D(%{J_c*k!uvJU$dUepCj!!KP*pSa|(AvL3Cp+3NnN- zFH>c%6hzkr5Do`tIgsjuJ%*~etF(y3#j7Xn1QF#-UPmPx#3*kNmuBp_e~cTH8Y)h% zLQ6e*O{Pg#+R1us&p%*5L8KRsggbq?6U#=(!9t)m=R+;gaAvAA|2cPSgY1vl2TGr(R8+`^34N~W#YFZx5 zN&KSVp?Lvqi~ey+Veh3$sOI6mPo?Xiht~TH=p(A>@9TrUpkwB<{Ns{ATZpFI1(rN9 zwxx`Pn*PA~*V<#i(>9e{NWWIimvNNc!sqA+Xm(S`X;5|wgs`BwNy)$Np}7fJ*O1jH zw~%ij1SAC!g0r- z`-z!^UnIZ-$>sX@e2yzF{J+Lf)fLc&41iWT#J4g6PNF$fdq)A$kIT7O@G;sR97rog z^y!t*L7Qv3iQQuA^y_X`!l&Hm`$LdrPQVNCb>SBY&=%<%_dTDVn>(^J>bF0d8<3gH z3};(wTzk7)-&cdK2;rNr9PsgYw$b+oAOp(N@*C<{7#$#2h_?o3+OLad-WH)YJ+Md{ zqPXFK@567xvEDLJ{0*Oq4o|xL;#FN;Z7KYs_9Gvv_t~)P0F3uKTa@n8Bd!MWgqqHq z$$-u%i#b;u(`ORr57g@Tq!;H3w*7ib+zw#G`)bj@NQ8^Jku4wVoEKida9t!Q1d=ZF z`54#OzJ7sQ5O_Zs#C4UK^C6N6?qv10hfK2L7j4oOR^w`uI0Znijnb19NHr}FcM1`g zumqz>i4^V*jcQ9o5wTHp!sYh!`z^pM5H+C85k;2NsvPjqKoqJB=#HwEEgc{OQ2KxK zhe0PW)8E~YMo0D2o9A-FwV=WewSV8-5Df4`+mN9AfemudeOL+oE1=8Ej86iS1YIy) zqr^v%2xMxKsu#9cun*HylG3`wqv21`Nu^MAx$7XJ z_F04Cg7SqA+T_ZyP7Y{IPn(EIRa8L`tp4q-R;8Z^LCO}Clp3E9%?-R)C<)Di*&{oK zrf{1(Z;y=UJ2oQ3mf5X@kg4j9)}#>Q@8tl*c#nF+bFd|aZSUm)3lS}Gka1$w&&A+> z{ZeyJ02G5hs7_?uo?P|`+SZ7E-%L?7UM~aUfg-X^Y!?Z|Q^t=VuZlQ}je|ebkW*`G zcR|-s)#Xj0L6;iAI%H_~ThjAjAC&lVDt)jDFC9(sV30oA>SLO%R=Zo9)FW;S*)-x@ z@WrP>bZ98~Jwx=w`rQ~u6nS$Z8D2ki$uh_`mnc}Z3cCd~pi#6!6|s&nG+ ze~qTrI>hl|-N&eGv6vfkit${Q$IOGkO#yyu7NZb)oKkDg8hYf~+Fr>7Zenvql#g%` z8)l&puLC4$=)s|1EeAGCo75aCQU&n*AgSB&CZuU68a{IU`*@_H((nqDJK_+B%it-| z;(daa}jLz5V^uZ=TICg4i9Z#yL3A!*++%zAX?)VuaEpTIjI^WY_l2^u^AE&kbh+lc}%i4 z9!jBc-$Ot=b-v~EYIaEdNe+puMD!xHi?!sQy$3bvDLoQDEkUt{Kiq|ML-M$GP@h$Z zPvXfn)Q!Fvu+M|L1fxj3*@s*CRjZ6nIs($j7hSuHRw9ak*D*n)fvHA(#hHJR5-hoN zH3_#d%@>-8{5%FKLUcm#EcDqSs#tIBLDKw64324|xXI84Ko8W{Y_S(2f_*X3%3yLr z1n&#dsRtvSV>&@}(bc<6QUWmOR?sx`^#z}s)TY?Zf2Pn!Fa$FQ9FS8wL+R(IL*0H`da*rp{vx zRBCnN74F77^Wr*7QnD2jwJIOv$;tHz$+0ZdE*UIgj0?;KO@*yghO8sAtQz&4&m~gvig@j~-&BF0@!|(1360PhcyKH5RkOlve4sNp5m9 z`~^CWAI9)--Mq5LQ2IS46C=sfMU(T41W4Gv9UFnsDWplJc_G+r@9yw}@7f;1hEDdL z%feNC>SEeobwfMkk8&V#TzrxRMP!#j3&zjhfLm*uxHjQ&;_Su0;6K5^*F_LMA>mgZ zhI-J{I?#^M%M^wct(#BQWrJ*hzO}CR+oUZ@HClYS8Cg}=2XA4DS_q{DgWzOE%TBgdT#`a402ok!; zyXoAI`X?L4S-0xGRTpbjj)=@#_%NFGk>hB3$cMCz)OrD0x(_J-_@~?vgWw;kv!kbh ziGz0jV2&QB16X01Q#Uqi7<>U<0Rs5zl`n9dQ(4?PaJo?D6M9qnBJe-M3QAqkwAl zEpbiv^e&Tgis2#4tu>%^r1900Z;85n7_1@s>Y+Q2IP-?LDS ze$a->(A0)T@zVF3sYU=K9Gv2Jl5#|!9M_uuBqI>0a}uHEi@WgwB&(+AiBWs zbr`02ClFU3)&1WesNpDoi*Q?069Yp329P7?AYp40iKs;%4CM3l=r@=M(mZZ1 zg3=UCzn23?@V?yQ_QUA_Msn-V1dJ&d=R|)M%zfg^j)v!XK))hoz(vQ~M;M+`fXD7D zfQqK_naCv4o1A~IhDbnqE_!*K=guzBLQQO*7uFj_vwc3i&>b7oT>ljnU04hfU}5}{ zkL$LMB;)|~ux_Y&{QJ*VA7IqwX9V=gwsWf>Vj{%wBp_au7o)oH%s!<;pc6uNnA(J( ztV_}wuq&{fqO52jdQ2SvcjC)k%+;+6>rdy+)^B<3!NMpIFmS6CBG}9T(^W@c+3%X>jdZZjV0}aQMQCap}1jr?nVG3O}!6X2+ux zbrkJfz%*!nk-}h7RVoi+v|bOHJJe7qR-K!ayiKr6rZFjdeSV99u2;aVY7 z#Frk|1Q#sc#`@wzP7wG#|MIYI+)POq9jj;H;uHOti!+CmBQ~2!@}Rab`Avgu(14GX z!8LW$?cwbD!(mvC&ON%wVh4C#n2arP@9_8FMUvw?8b+|<8NaOejW_)5YBV>rr6DBY zQM(rtwND=HQ&MQ5??@5H>tb;3GWZs{&}v<{G^yIs*Vom2=R8S9IP1DabF?qgz-le% zvMG&kc&rnje?E05xRbFKdhLATTmdO)ZdkRU{_E&eJ5h3F7FYffV2r}g_C!v2J$M$Y8svAN$2`Ey+};b8jU?KZ<{_3;m4wDj`4PBLA7 z8=OGmczn%etVU=1YKN8CWFB{QIPAk0qpa){8b00yFuZp(J&s!(O;L~Vh4X}*rp zpM_((w!A*>^zmDJRL+BT!yJ|8g^g>qJW>;l9#&|sKj`s$q2~9N>QT8FGcL{;!dV6} z9{=9gHS1pl4h*ohVWqj3Z3-Jf`|?>=6#844oC(UfqcpmN?IL&Qm*RM7fQ`&rYdPL7 z+YokNk52b6%(edcFy@%7wds<3J#CS@=SQFEoqX;m|7`Z~D9ydhG9HEJk%Ns_FqZ7m zFaVbe#zD!N?z-&dH=3^ zsK1r!a>Ls4@z1PW?zF%gb3#=QVDuM%K&a7*FZoz}fo1gK+JWxIbz4?kK`5KvAF%KZ zb5VT1a^$lyEE7dLp`0YLSUeq|N8LFNr>5QG`rdLU4b!reEM&7zwWv?D9!Z;XpS9~u z|K!@4zIlov-4N{0McsuZ$LnGFWjxVBOWRr$Nr`S1Mv#WSqI6)v)PM`Z^EHv*Moal< z$Ad68x~z|WWLHlUjA1c_{l%RPle9jC99ovqSjE0`#rqePHB1(jm$W8YXP?NgoqauW zu&-Jg$9z4L%j7M^O??9 z*nWSLY&Z-J;gLr3))#{7+^)~Ls~asUTOZ&ks9Hr<#j4OQBuWsUO3hnii-o+vsIrF# z+(bYNohRWOoL8ZJdbE;h-R%~)Hu(5+>EAY5JjHrQ@$JWwJ9pzt-aZ+7`**u{>hna) z9-9?sw}?4k5nEx3ZHTBSjy%X`gYmc?V7L%*&L?|z5K1W=`ansqN9bRB~hWN_p$2L{auun&GEdU%jJS>SvtwzMZCBiB7`SG@ZuvmWtNtcXUNp z7+_V-1Gs}%(FG>?2Cxx9oV)T!=y?&CS^8iBFo=TmO>k+`;EEOQV1VXMSnS&g%yC%I z&FQ(dxmj({&C2sE*eMx*!Mq$Q_RQg2 zMSVeUebiikpy#?||5ZHL{FxYkQQj+{IH-cc?rH4UfkTpxeMK_MG8K3fAyZ(U5Cz*nbuZImhjpFH^*h4DJ#0KX&UL~lX z7b7K3>rqg2a2y}_dPucwpoAwmBi8e?E;st+4LqFYvwo731w|Zn6d`u2MyrwPMr1&S zbvQp}8H~n9ix-QBNt72}W}W=L`9m;8`i~v3J^6BTwPOva9yHsdiPZ)i(4(rYLp|1e z;JbH9z&T|wvj;>_DATl^DK$*qdR|-IU~vNnoO1~pNN*~|_RM99{14jU_Qub`vt7ZR zA72GS!%8MSIW_0$fS;(y3SCSxqnwA+zzw>8nllfw6CX8HWZS?aA1U2LdcANT`^z@tF$qnb*?4^w)tS_EP*#k@{dbs^r z_VtpY%atXzqpqErCFQWOCG8>VYo5Z^aTZe}amPtep}^Yhob>kw7<%D@d1a#V7O ze=l=l2eqnllb`RibC1pavib4r2j6KU4*GEre!4pP-S4-F)B9l?L02opk3A9P2Y60XYXfbYNJ8phJE+Tcnm;;q z9H<+>FzPg3TlH~E!7HDe0d;~A4C#WDW3bYoYNkoiug`{tdZb4w1QDP%#Vu8;vtL~- z`FueEG{E;iZ%ON0+PU}ISXF~49Q03{`!gonpc4mb>|@DEc{JC$Ibh+K6>vo8BfxoU zQ8uu9b|&o2_W2uVgI5$-X7IgL+Qq5#LHnG}GfU2J8&#vq624JItf(I}{~I1?XN!F0 zBocvn>E}J7a(~}cIsYyz#Pq@Iwg1#Obebf()ATOHM&I}3@nwm5@6*{YHJWJ!+&$j_ z;-NVp4aM4s_dUqt(_(NKCZ{AOwEp<}uhu_)O~rlSjxsFSFK7j0x<}`x z?n#=S{tQf6`NCMQbpOdTl_T@9;&2X?Y!e&B4{Ls2i3Buz{VZ+am^C&syTEp}Z3J~a zwIY$`+K~5I*tpT*P=D+5JNwc?H4GOIah&>!UBq*K$tq1Llg|sqe)Wa^UMx)-suv8C z3O-u#&TUl<#Z+SoD1H6g3}yCAq8MjJ7+j&+61(eu%l9eG({WFBlV-c>OD4OpXL;(_ zjltNjt0@N{;Il|RJl^Wi={=`3p*FX}BWcXVL7b?*3FgjS`WMP5v{sxn(rE@3oXStu zv_I&RDyC34|8uhM^K|*$!TPfs#{|d`?Lme6Gz#Z#tFu?VJ?zsGcSw*n)*qe3fBSjM z+feI@SF2C$Q{yb+PZy(rK|1Di_55Xh-i+oV{@FDI^bGWCtBnK2!2DB(=l3)EV*N##tx6(e%wfOWVWhHi^rsyS_cV5iZ>uj-fkJ&44Vs-;-nOPG8? zbuvs>ZXFKr)&<0?wEh#+vR$pactYimh7L51KKt`6W$kPY791x~{f_X13hsPsx|hb0;I?o(a=iugiVgRpNq*8{Bg zn`g%EQ(BK}%7^|ge|b##)w?XM`T!p?S*B^=CqMB#xpp;a{B_8oHE)CMkG_!_D{;PG zT(Wao)AIsnP>T_HKawdlXKlZ0K%vIBIA@EU0t0AVAJREI`cGzG{eoGs zTJBO%ChbOKbDzAYETK308B7CFTK+n=wQ%e;<#frmfEHQ^x25g8rrn*EJ;PKN0;)(A-m>e55Om62ACok{1dNmq=1P09aG(eU7l)wX@vYCbAb zYe|vcMO&36sCZ^Ju2QpmDY?Xsd;jx4k1j^^s9nbSli!}ocm1Y+1-fQGfAYE}{F|+4 zM51=$-Eg1pOWbr92+1jCeQ%$IeR!KaED3kzlj)UxFHMiRy!)_9)T*H!$_%+XT<*ys8|n|!Yj$E3D{yCzJ~I{1BsU+#cOw9~?baqEiWlLlQr?Ph{+v(q=}Brv@Y& z62<=*Z=6lZ@etUabt=`rbLuUv(~+J|JIzL4ls*OZOv`G4()YHvuzA3&uj2&weK%Tb z`j6{QC`#A{R07lf@iZy5+khfNT7l_et=s%*iRAecVNb0(hxm$1Zk889IYK~xi8{Dn zv1fG_CbT`8@BV#ZbA^$H&7T5wE=@>lp!sXgGUV*~7;B%1HrDbZN+Bj@nvo^2F4 zDZr5s_DiQ&Cog=-v-L+rT7~iV!jd@8k%WLrbv_BJ=Q$!vr5{ONE_KJ5C>8X4Yj|(` z9K~-&I_Hj#cGj;G9H;(L145RwTyiHX-RAUcDBmfT^$>b0d>#VvZv&<6Zk3G;w-siZ{zL zrdiW%_h>A5dW>*qnbFev$Y9gAq$HWl`^Cnm&HU(sj`h`!aO`ZE;l>2D{ax&HaRn{@ zozKsO-F+%tA!ou?0|_vc(QSL^llD|eS4r2~$`ZYG#vijYzLtt7J~l^lo}P37;_=4U zl%?k3j49a(j%#OS<{$G-v+_{Cf?u4G^R3;`%iYILUFT5FQ_cmgG_~;QTVRExk|fxu z(r4V>>;)pDsP4i3x7EecTHlhbIztz|Xfr|dLbaS7t6-br{bt5W!+YdP*940p?!en_qLnrJDlVi7K&t60I&~G_D)wW*l9V5_XCM zSnj_VdlPAq{|MBojh0$fDRa~{Zn&m>2#`t06~Ow}T>)zdS^mx;Rw>FW zXAgrJ_g)Q>KgFJ9X}>lYXT6|E}` zE>rVbqwqfBs1S$M)OIYWugT7YSWk{auJ4A(eV=-_OnA3j`H%<)AC`3?{tno6s`%f91 zM|SJHLs?@@-K49>`q9*OkQh|en0@t_z^;+YwMrAUzYMs(XTva1aH8tfIZif87tZ*Hm0F2FmVveb(wJ zWjLgydB}P8@&TFbV?slg?;c4KqL7E&x&FZAE_Dyzu^%NhT?96$dj4V;EL{Q;k6a6& zw=|z>6RuU3rLPxqB!*QcetodzwfG)7LTgPXy+(9T2S`*BHitkLNi_iu$E#goxKA=3 zX+wuW?WlnHA8(dz5CdLV161h2RG3lx5Ms7pkze?OTT*58U-!2i%|g2Dat=J&Z+H05^(P-8+yF8K z;YcE)&RsBM2VT9N4NX}m2XqpLAZ^n3nKfs)=)9!}@zkfB9C9at`<83U^_&)PVU1Ir zWmIDpOPsoFd3{aaY?&BdS<0g9znsqdE;sShe~rSMY&U+6#;ZZ097!A4E*tlRbWU{U zO!L0_osv^f>=oq;+17mU2I)yMKU-QX6jpMA5b9=`}c5kb-EDAf^PV!F^9Le~g&R5D(V+lzzaJaLQOS)!; zr&&F|3MiJnEGakPqpI`nQ1ahJD?N7DlGADJgZ5tj@>&tt6vA9SB#L;OE3k4qBu1`K zfQbA?ty->s>YP7g?O)mXpe|ZcCDmYi!Q7Ce_2ynR{+%G_)Ysqu{60aNO{=ZpI@usgw=UDHd-5In>DtaWUAv2=BQYGNEwXMK><8@r-aB%RfkXwrB1OCNY?^bW6@xl@1-1#hZF4#Q* z20&GBaH!T_rVblj-`cq&_P5U+aIzaQ2#t+H*_pry`xiu9AoI0}?D0J<|@e)O`{6P-Uqb*Y+p~g@i({ zGXzR`DcrdquuW#Vfg9Dj4a9I{sChDej@0-}dr>L&q)c6N%EuxI{VBA*&-tq3FC03{ z`A4`_&x0G2w4v5=O5KLGk>By^Bd^gFrbe$6E4}?$)4Yk<=iitpMbkc+PxMr&pOCD{4zM6zRpmkF6 zCnAitt-e0|kaW&LBi86u$8$bKPHs-f&OVv3>C{^x+^M7zA|#azPg7g;xo&MZv|1@L zAaU;TNVZE|-OP4+SyQ|)BpS3nC9L0g>&S^=^Zgv}m>XX)O(0uKgCJED+7H^u-{6lV zWxK+ZXSC}UJaa*Ulkyc5z4#WDOivGohd|zv6Vzg;NAaiAjw0s`?CP==1_r259QQ3G zTJP1n3ZpK@k1dxJvFlM}5wV869bC*F&DZzfh;|_q;fZ~VQ=hdxOv7w9p1WjM_e^78 zAm!~;)4)l^WA=M?OOLk?8?%hZ7b>c50y1hQKb>s=>q-{f2n(zHXQ$_?J7SOqvHIcGdJGsg4lsB?$NSsn^-pA@WaWLGEBXl7uH&q z#oj!vdfuv2NU$R@b3T~F2`*fkTXCZHA-ov8)kY2cZH)ItMS+YRK@l8ePPWT2*TBVa z6P+{)uX!k@+n40>?=)fnM!avlb<~LRHSL@AuplSXz=LjJly?dke5e(o%0T;jo*w0x z+h6^})<&-~XWU1lz$Lj2v+2d zL3Kp5PO-T7q+8bwq)P6X3F>8^0~-K|vIC`9I}^acM}bp%oL)5ruc`s767QQF@J8Nj zKt6P?SSLMR81kGCVVBs0sEmeLM$|EG7$P_-tV%l|6Y=WuDc&CQI9td|_coa%$J7p| zW^dH&uO3zs8?r<(sjcd$&KYwv>F;g)dj8i2k3+!+=SV_C^^mi0r)_>?M&}+}auIXm zF~|5#XPNB%a9Lp5;3IYRs%xtbBjTgEVeQp|f$Vh=9^f?)yOKfhMbyBL4SJoOVko49 zT-=_(20v95R_*6*9@p#*T5#%4dzpPqO@-n1mn>hg{yP+Pj|$liMOROLH`!s#X!MQ3 zwUCdcKY0d#;o7|ju+4~`ETm55{cQ{>Ru--76z+{)H|_sZ3jmXl!K5*yz%EezAkTtl z%y~G{LJ7G~qDMD*dmn{BhaOfiAj`5iU9JwRhxOz~ACRf*msfEmKY&OW5iIjJVC$bX z;3XjLJ!Cvl@1VG6=aKnm`sRu!2Gk>>!h1j|&`q1lSSSKx;D!|K1#XG=A1_z7AFf(x z7TQPpVmH1=w@>yCy8f%Ej?g+_Txkjc z%fxY!2xn^%LMk%Ma|{<-=6`z5_27R?M1;0;|2%WS3)1(v~c3z4H2OYWpqeirtY4xA=MJNf+7{Ue?I*{sFo@(R6&hRN5@ zI{YNh{4Np%BC(coEAJ6}xXE1=>wz7+%#|8v$%mZx0rQ}aTjBZ&c5t?Uy*k>`aXO!4@KV4t&>u_JAz(_wIJp%ynce;ou&3j=nY`8>vNfZP;CddD5NoDO} zDr6R7TV?F-RUJ@=M9AR~d}>RrC2up}!1&dc2q9SAeP)clYhi7=%T@8QGUDp*WGha_ zb5Ie+b+Ab?C69%{5KvkNo5C)B@VpOo_Ssj#-IP6la9u*u{ow8v){SH2LwEjQ>IRv^ zrH=iDsxMh1{RCaT=>b*GvrRp*q@t_Is?w`sQ`L@C;1i zsmWZ3Xq-u)ZZdjdZL>}4@|*7dX$pR?vH$x$b@q7b9BV|YT2R0jmlWH`RM3YV_SviA zSasB6pB!9hj!=M2_#cWz?54K$ZEJrGOYrMHFaHv?83>BjJ=u5CZuyZmCLGNDoNm8pVp#QL0K5rYY z1?q^?lQkQYcLU`I>OQfjPMAm2T|{v8Td(1&ufQlt?Cx$(n#>Y7$_BWeybh3ZN$=^@ z@evw_XWL$8)WKE})g{cZ`JhQU{CY;SiHVlz*EWI5>;W4H#tuSJ!kn|M2=NAdZTdm!Y-iqQwYMERKB!L&1JM~%-(0xZ99@_@=&;vK zNC6rd9HfW3I;m3F6k*tBUk7)7;$gVvi9@|4W2IZwF}R5Qv`hMV#6u{0pzZ*crwByd zBdfD@Po2V8;DsoF-XRy)4GuqxZ5^b5d{Rbk*7&eLC{I8U<>ipoXoUbuM96svoN z7l1Hzwu}5KQX3lDz;8xrPAYhPMFP{ZD+<9mdZx~<*?(7^{x_3Pe1B3rUypQLi1qOl z34nCCUVs=M-<0*Uz|l;me%DfmY1wd1=KqclP%`#_RHLbwF|0q)x|17=Wu^oI%|&%} zwr$D9k@u`;94@Fkoi(4}fV~Yep!`+KX93orJmB@97uaE-r`gxGaa6r$u5kWRxCHSV zU#ehVds+x8Rj{jpM<)(|(?g+-07X^~R~-W-PJRkwP$$=?KiUoomdXoy(b97>@uNKY z+|$`u@af=?6!<&i2HYYg1_o$q7sM!Z1zFG{b{er8cVT)`?>~sk2fIu-Fe$8K{q!nza6&E2X1WBHEDv#dS z14B}e+27|r0h2JVM?*l2m|ke~foi=?DVwQ+>)l?E01X#CT{5`?s_zIx8J-s~xBiRE zQnMzY^@u-EXu1q#VSwnQ86MK`8^HXOac5=6DrHA}74{Vy?W1Fya`v7T+W;?;XW?yjx1#jB?T%poV86pK5iV8kQ=5c=rPGvPGYMxd^5 zy7fW>=)Mp07X9U8+>^ zD4)woW)(aPdky3Z3R(ndrwRJEf=o@6?RH~O57bNZ62-U+raPCTyRLUPjNC7Ipra56 zp@@9R%RHZVaj;e?L=o^y>}ENItA#ttfiU<*Re-|@e<_XZISi;VJ}*R?^MD!>>BkVU zFl_Vac;#H4F3Q|<%|DffUq^~$v4`tLCqpzfIN7mTFMWh#?;)b8Dx_Z#=d*}z*DScRC@gfBIdX=z(L7`lB z6!CgyyD%;!L}srE6moK7>v;+Xvn(1$BD0)6jVvoJJF}_Y_3!*4Lh($S>5v+62(JeZ zjdB!gSu;REtNhytjd13S5RV?n=Z(Z=Zw5FPFiyIyzwmNqlX2u^AaLXl4fr8emBHKY z63a*OvJxR0lm{M%fs3M#*=Y1StaF7I#1;`yQxXE%2Yd|xvSQyuCE;Fm&-To;1M3O( z&=u`cU?7Nd1ZXqv#d?DpaW13+vgWf<=_{)~Vno7@^ukx|sCyYN zV5u)X*b#jyDD=VxM_K6O21F{rez8UPmm)rPT@GLHE6}97so1fk>9al>`=M^_toe)@ zt0t7DLANk)&_&oIu!&H+-D2X8Buy=}p>r|n`t24b5?}n@9P$V(sBkwfi;o{v<02lK zJ>k;L0?tax9V<~YqNa>HJTm=ZF3Y`iROGl(uls+NJ7lKt4pR+qIK9N}-+h)Hwd0sL z7R(Xi3=|RkMPJ{Iekv8fpXjGSDCw*5-qAOg8bKM` z1d#tZYXJELyd`NyN}*%(KaCFC>(e-l-vfX}fy0upS}Jr0f&HeR2kxf8g9GLOCc+%5 zV$mrY&}cVk_hpLU-N|+Cbm&%kl5m$w@fl8gqttC05T^|h9MG8tRU*$CgU2v zj`nkhFj>;n3V%V_5%ryBF^ee#)zqo{RsGiik9$?l+`hpXaNqVF8`AT+Iru=G-8jYl zAXLwQOETZ0VUqNKsRfboYpgoQU%~s*%O+0adaPMegcQhy5XF%n`-6xl=F0^uBxb%~ zZl3Y_=sm?V-`b-EPu+u0L}Ck~><0fpcRfEF2_A5ds%r6&@qS{QiMKl);<~=Bh8;?5 zA3k`~EtXC&o~AfO+8i+6)7SlS^{(0Q3t$|oj2|k!n*nXn?!1Y3hSkq&tPv>Nu$crt zZHf_`tpQOi@&)HOrV|(>l!e>fm!XWndah;c59iC=>?8F;o;Z^j5z{pTNjc*LY5oZ9DGZ5yBf%a?J>A!{dUfX$MA`Do4D|2MP zM^Zl1gB2_@;9IDL!gu4yE|q%$Vq>O^z4#JGrgn}=Xgt88S_1fGaP9*2k@`J`6LhSt zWe`WL4j%PfVa^R2H14IvDi`yEI{^kVs>D)H`xjoDtfz9^6hKvQsa zP)8vs({ae>^Iig~yqI{=rlSJo&_aNO9WU>x_f5f& zfty^!?HODBKIvN#(^v=mcEgv>!;?pQYzK~37Q6|bIG#Sh>%HPCOH0Np=D(*~j#{%% zL{Zz1NRNxKUejhTI{gvj)wl@;3;irgOH%o9 zjUE(yWpy^QU>d2C04`iQU-eq&2yjv4i0a;mhbP-9$T%Hv{Y9;p!CO@Tuu{Kkj&kUZ z-FVM3weCk4z2s+1O;C9&1o>plgC#F0{4R{M3NeX1umdx74shhi#E}`Wj~i6$AbtD> zSC_W~1G(V<-SIsLN;QcXPD%aX#L8hG{CeY zuAwLRAgHDD0A3gw_J!xBzhX{7pDT8rX#N)x8}MBa+WHnI@j3;@6;U4n?^0(m1H0j} zOC(m3Pa*_o-?PxGH6?@ZUqOyTGb<-%PAbCV%3z&s+%=Y#l>kWZ;8`eKUZNg^MtEp2 z%LYQw$JIWvKbbZYLF3mKCCFR(&akw}^f@8n_~Q!{PwmV$ZMFsQ?6yYS%l&`>I9ZK# zsCOuI>{E1l^GPQfA8$<(NT<~u#jPD;gSTDlUa9FAC${cF!Z-SY;Hhn(0W$dE_F+6X zk}Jb-45_a}#vg9^ap>k@;>JVz4^WzO&c`)Rd>NCWRrle6!{Mel2iSY{GB+de?e%#83KDVBhCSlnrUm{Ec9jYao3 zUJC2T0>$CmG{k8LCtC;sxcvZJaUu&85QJIY7F*!)c949b91rDTtz?Q`75F`1wR5+) z94T9o-RK{JRArw}Lrap6Cz~C`cn<~@k2eLRKT+F!0Z~jEfy0~CB#ZLkr;a?#pp4<8 z=+HoAv?6dNwCy;Ebux;%Cl2AhDpc>96lEyC1(wss>h=1(%6vX6D)MSk40rek)@Ymt z@$n%@nf%L_We@l~rfi{KgV6ua+L^j03J?#(alvzA>AZ!t=joxW(=l%h;`aEnqj3iK z3XYiKocJgDOQ9A{l>M@N)ro2f|8i1*!Snn^x&=w+8%0 zG1t1mbq5e|?TAVdM~x-E$qOs00xi56%C;ZC0P(^a)I*|>f@5~$Fk4d{sb(9p#{krh zTVtbePa7A=k`ERF!desHv6~6zAimfF>h1KUiCB!Mr|`z$05=)((0#Ts*EaLQ=?tQn z`H!A=l7a3eEC{|M*?+^cT>(MVCktOuhAnVpkqx|6QL!M|DH+$2%M~_c=K)R#A!fKW zj_W}VQIn!%9N^lWeEnEaz(|;3n*ZO#?s#I{z8Vz1cn5F``obsz&eVWs!v=zS1T%CA z&~eln_-FijSaEV<`0X$$ z_-5?*;M{>dpoQraek)!maRlCaei;56ZkXJSHu>xajgNSVhhQD)+hJM;70;c-q9DkI zvLWNWjGkUe+;Rr5nCpMsS<`)B)#KM&cewa%@S5VFc5AlPkeNh>E3g5=II4O8cm_n{ zkPhj_m>582msh=Bwl-g`XdwrMd$y5L!R|#26<0Ic>Lko{1PNbdEl_y{~Xer1l`l z5WO0N2Hy*Fa>IWNf8bI49VK86;}?W)Q&B*@syi?-sSX~K>>Z?k5mG)8ycXuSv1(wx zf-Z0#BwCpaa?+EYgxwj2)QG|U`&$|OEfe4=4G7?IIl&qc50#DgTmVRzFXD}_#a1h@ z%)~GT$5Bfn_`wzCvS5Hbj3QtAA511BQpwPl2W;|54==8%8&o?OnxX&VBAtngmM>17 zXX@}880cTTHzg}E*R)TQ@jA!>L=k#&{*_o&=^w%PH#!S&;@ORl;Um0=hoIhwVNe_N zUVv&U0<-VZkYJ62FTzr&YV;`g3L>Cn z2B^~N!^dUpfDHz`ERHZKhd+i+UEP4D;uZmIHNaWKC(OeeQyQ80qjLhhe=(&KE~Ssk z-DgXZ3V3$~I1Ep^OS%$sF&>%pvESXEF?W9pn^ zE)5(=w5;x!*G{SM6)U^tD1PJ7O?8bc{>l}E?_$2>l$ZlfJz^?mm;op886q&$vdcCJ zUx{<4AX^SE;@6m5420QAyNIDj{^F{P$vRto@u7u`~b$r{WHi62ao6^loI zjDUCh|HEyET>pzJ*h?R|9Z>g;x#sDfnhv;&d4HSC|1RYj0NU#$%ucw>z%{@T z|9{tDgx4o7Pgn~oXGB=k;l_`h|F0GRZqj~5;ja()5BlK`Q!J(iqIp{xA}jua=QTHq zOTAw+6;aPSf}4R5Qh@Xxb$po{n17%Ld;@`8##9Y?i_1v&GD@|mU4R|krBqs_z^QAV$yvA~c3WFao%|7!B{sS=)RF*1W zkBJ?uznaGE8HGQ`-1zgWM&SPpnHHyMxtFf-435g!3pGy>_sAL}(0^WZ7;nOYEAqeO znF#ikk)x1V>9_4A)$Uy(0{Ygkc0b`0OaYcpp^Lus&fPI|09DfiMK$X z0UB>rf@4M4PvBf3R0cA=>JHc^!}f!F8FCbgH?!isVVg?W9$3Q;=e4Lx4}kPWOg&67 zZ+Q&5*6n{6;0g5Wc#A!+cps340ceN054Qd zDMeLQ`k1`(85`a0%=IK)s{}+5S`qOZ?_PtxG z!BCbgg^{cwOJm74)s1eJYNn9AD>U}98_SfUxz@5~7a@DelJ)<*x{cre{e14{#>ac! zbDrn>?B|>Z3;S%DXd{=8puiPe*SeC2LC_$Npz`}Yq^}o+R<3|LzI+k$0LYYtf-5N( zw3whO1`Scc*@DCZ)aDXFj{k@Gfy#if_33#+%0*cT=WYa_v(J$Blh@FWy|bp1!C{SYoN0J==D83hn zO>k$-PDG&F29jU!qdH1(eAOa~3O!WQL@fddMAzI(4Qv~{-89*m$5A>sb=!U48 zU2&KVAu6F!fwq`#d@+`S&WW&-*_ zq)&k%ue5AkvRK+-VhSYKi64sezMBX#?Y|*7{2K9i2?*gT(f)2I2v|HZbi^9q_{Un| ze;z5)x9tHx+wH_RKBO!qX8S7OpQK13+$D3C#vXf>FV&3K3vo4RbE^yU>_$C#S}p(m zFD9#^Sw<|#<3;uV@C}jQNIFYYKFWCha9&!2ikMza78#Xlg(yvxW^8BdH5No86I=&d zYfw9gq}Ok(A$>MMwnWG_AnIcz)K;;_y~S^#JvO&fXgw9O@3ReT!#h7gQ6hetR)?#{ zuKa$FCpuj$95x}oxmw?o(H{?DEm1mS+>WzX=R(YaM4kBkQCgHwjZjOe{~z8D z1~yGcbOP?4#{eJas-)stol!kZT!RdN^?G8k_UUi5<724o!U~p@pn76LNG9!Y03u1eFDr z##fr5oFB`Bi!MUy$r_GSz{Qp*j*A(ZEwmdFFS(x4{Y<*VHi$Q1XGi9CqMi~K&Qmeb zg1fIOM4YOTLoq-*-c|GZCHx5lmj5zTf+xT`cm2E99$C@0_ldLlN8LW|X2IF`kI}lS zXGhl$S8<|$=Y~eC1zr#xv*k-I8oJA0z_%x2JkiGGK%fm zM+};eqcooqqyMAC%$E#vXbc53jnl8M)3@{*zx8dMN)HqpG&HWPy$>%Eng8F^4^-I^ zEybld+nAgN)aa)Oo5txWpl-aJ;bOD1w0&LwP2Xbu^WM++5Dhrr^lR?4_Ts0Hq^9R^ zebA7VmIVaP5r$hGVS(N)HbK_1IRq6o0Jv9PE2y zqOFzGP7{T6-mQz|v_z$$K+F;dS~!DUjm+{Vq}hPacp21H$(6&#rys^YXvIdn+lF95 zIEVQqxw){5U`&ucb8|oos@D7NZG0Ql&jETQC)Z78)PcpJI8{~p``~V(RQpv0p-RIp zG$(~z@_hj_?z%{3kqzoOdLYRP58*c4(appym#!+bzG+seRs?gOmj?RRQ+FV+ z5GHT;|0W-(`1f7`tMCYHImA)qk4Jm}Z7d3ZU)VxC)p6B(VOPqK9NhpE9RlYcWzO7P zS56d0)ROm4`gUSq5fDAR{(zwCX89H-*8`TM%f@FBokjxZkhFPy7!#7Xj+P%sDr@e- zwKF?@i_2=-Gw2Ew8(f|49=mxCb~ymE{n`zJW^O9T z9X|(StosV6plWEV#J-I};da&|9tBR+^HfYP#JI!~z8kFV!J1Hpg|33XR9sV6!^ykp zMQqy;S4jr)B8nb8k}5`5Fa)w1CBxN!!bVu?IdcwI3?{W@r|W?R{#8is5DUNqtNn;w zKF$mIxed%q>AZ`IJ)#Kg4gZY3pSOhJg10nyrD$x^{9}+kfw{yAgLXzQM5=8?9mfAj z9+-jUdkMJJ=`{+HpI{as1%G>5a$V}j=INbq@e(LVmd|0Wz@v`JT%W>jM)+gq z0T^C$C0WICM5WANcw2VToB(mOTXC4;Fe_uCvzTe0nSvIg9pC>qajF_ z1h_|o%48bvK16RD-HgAuxX#DT^;hm~>l2Sb=jFNy@CD@SndLuY|Ll$Cde}ugCiOcA z$g6@(2pW~l)$b+T;@DSjCsAJIB~H`Mx^f`W$2;_y7JNT#{riDD@O_4j%}Z&R?%Neg zUk@*HjE!5EXky*M#W3r`KUK@GGp#y2fjdw(E0ET8r>i8Y{50_GrHah*~C^F*SchV|=p zjS#3Ap0PyTHDZ|wgeJM3F{l;`AH-mJ2MlSY;?e?J_3kV8nSfee*JF1eI>oo@l2Ktp zbZ-4Nn~9n*yHfN_i(iLPP|Oo15r~5N?&(T%8j9UlMnw3MuY|j#p^nZSP|*0fR<}BS zq_2s=NO53qANQ|`pj#`Dt9)Nkh#gj{nO1JN^CvBKSyhX0v5Tqthw3js3r!@@#^c_u z`7vvz`%y1xn0)_gt<#zW);)A3@H#p$hx< zrzM`_JS@nQ`(9|ZZF%3{lQ}4p8PlbB!6(t-|xQ#;{7}u+TLSJEcgqS4t zX9J%Tx;C>74Q&LL;W|oCWvZ6DnIO|NmmYr=1@BT`Rrx0^&lzUnQvx-X#_vUGWbQo**;-20nn#^VBJfXyVzv~?V&H+WGcY;4#Q&C zQC|VVMil@3=p_|Bipo^#_!O)zejJ87KmK5$o~`2scamXkr#TwBz^SvD$Ab;v_}4fr zSHKlt!W)x-H2Vuky>-34ByW}Bf;wP`(=)yA=l-8pA_urm+_g#WDH0LmYy^sQcfF!y-eN zpa#gmV+k;_z%}kFEMmC^Y&`y~KWVCErzA75;bkWVHas&OUccZ14(A-BCjig5gx$jU z5xgj45v^bYa#-c%-V|k!V=tcl6)B&2qrfeBF^04HXcU!NghFZA6#^mkpQL_PYpB66 zvZakB>rlvn>&hcrc4|bpk);~5)adi0oB95(sHX6|0~exuh;%b;(M`tKesN!DLkosW^|Y9vq9cq{Nkg#$7}YfVW@FzbV~j99xOAyidZyYn7Ls}>fv zVC@!nt$D#ojSW9v1lZdCS_t*?ZEeYL{0bUUtRobERrkcQgp4k$z?};s-k)G>aGK5Z z+{|OXEs9%6vcXA#-`JoTdQ*kO73q>B7J+I4nin9dQgzWf#$gZAU?Ax-w-?gn`NZn+ zztiH&o%5J^l&G{9#o2eHI^TvsJ!+K!6iIFu{Bv2$&$oOOf@Uy)H6n{Q^m&1iX3zSq zu>`-Y2XJb!R{Ro}B(J^4FXKyfrI*()V1`{*zX2!74cYdz!}zoEXuu-r7UOL%5S10< zeUIH@=2FlM!nKoW=@s48i78;V_m@|lxCK;;cclYKA`4tj48yMJ)Ih8;J~Acm!W`^I zM!lRqV!X7#7%|enZhFUuS8^=&U2xfJkTu72`Y<|UH06g5Rc*RF#fltYByI3!oXd@e z&kr#@@cP{;#WH!@_9RcSq`p!3@5T> zkX2B%jMTYVvS%(tSd|JO_y~r{GJp~~q%5u$F}0ogKCOtJm^%!|b|9g1xZHN3+uNdx zDYOZ?90XajP8S4o9(I9|s5iAx-Ba-6Y$!yKCm6wcRCFh%0c8KA9sd~u@j-Do`}ys}Ol@ zGh0{EiS0%NL}n0Z6k=t9cO~~nK^C4lY~e)ZdoW(Kvjns@gJ-XTyTCtfupY$3Z2nSL z<7TSD18a2+62ya4CFVUd;;@{VE%<;4{&1$=`^t&3>tO{A!xxq+iwLm0=I^)#9sw*L|o4?6f*@b6arcIb&Yr);wG~2NeFcz zVWU0-!$ELHNG~Y(b8K*>jF_}v${KkvuZY!*z{}MI>s<0m=$~iH8@aZ8kqr^rh~RAs z(SYCX+dSu59$0AN{EhWEyvo038sC*x#Ig`;6x8s&@bh;2?q6!j6y$B;uWbG-RUg>w zEvd+u+A(n8Y5UCI_R3)BMx;y`8yA>IA|J3@$Dz~1B$mfTtZeaA4gp0*hv+^pmk@dP~N(}nJ&!$LhScNR z?L`du9W-vIA#IXc<#~3=ea8H1F_za(;Podz@7WExbpgF^pQTa;9;`L= ztXSr|WnM!9rc8$ei?}sJBq@mgEi7wMX+u|6)}8Rb9mTiraZIiZoMIne zU=Kah@o~hb_2V0%r~N1Pgq`s%UzuD`*#_GwpfSVxgCDWQx?JLAx^^J;zP}XOPRR&I z_H#1d#94ci)T^JJO)f~L^(gC>@dWCX$=zDrYz5J-oI_U$GV&qau;HWC*q;bevg>z{ zF@6U-#*D{7hr1g|o0FMGYBWjIR!tfb2klVlHae2GamqS4lNHffvrUV$2W4;ZfEqOz z!e*KAGO6Btq?Far5dlsObgcSscKdbAc#&(E*!$1+K&u^%gds|w0A97{xrJR)feo#P zFWJIl3SPN0dyIP-(y%!WRWJFY??3~tVJ8I74sBw7jWhT95WO1av8PPSJ#H|EaIlf= z2CaoMDi!F?V~6mx?fPw!xfnZV?OU&vw54%nt=pq7GFN&&+G79}l=eJRQVjZXsdm^k z!9sD^O7RtX$NU<3>B`3v#3VP8B6OzTaJ&SYc8r%eTV!vJjNjtLuAaMFy{}N5m2E&_ z*0lctCKV@otA8o#CKn6r3$g(knvk0e7dm`U8pi+m+koU8&m#Ldns3VZ6k=jU?j}S< z+png%2_mQDao?X2cIF6M2>lc#gdNu4DN)qHomml86Qv1tX+As#VOEa}9R~}-Z6_@Y zSA{VV1*fN4e;0wA8VMx%%n+ii?;|s-nekQ8@!OS#cYcOcf%yz^bCjJ-sv^+Jy`S8y zGw)I7ajv-)=NiRPU3?DL$|7eSd$p$s2uysgmazPxWV1<j|9F&Rw3dnE0_erczbCc9O9sJ&D6n_g7pWIz|PF-ye*JpCtuW#j470T3? zP_MsYQ~jXa)BLZoAELd!^Y41qlbO?OACw1(H##2Q%-}p-MfUaRHTg&@d!R|Y<6A!Y zOl}PcI9BzmN5H1?2{AfBM}ghGUx{9gIev+k*>}iI?)ZfMakt72jbN1Bl7S@{9tzT%0KcBe`w9Bp2b=uk1mNCO<|1I z@KduKjHTCM_V9jE&(=@B5hs(yLUMfj6!S9^fP@J#Jg~j7{=#U9ef-r}CV6o<`e872 zat3JL>H_}&ns?d*I@@z1v9@mGERs&cl+~A-B2nLPo>LYdq+g7xV^?+WYKKebER{h| zmGy_{FXLd2$V?yN)+O{5ZJ+DJjw<-I6m3WL>5#heB$(fyBzf&T|880^CH$L@$YbX= zA%5D9FpzFYo(YoqDs9(P_8nKdX0`?SF^owFzEGxBHoWa!P4gA8nulp8e>iL`>%(&b zQ{(%SX_SFigoESB7epS9YIaH^3->Rex(31S6B1a7TO7aH^@?xggWG6Sd}{c&gE$$Y z`J5U(gMfP4>XI%nRrkk(51Rew22!4Ac-6e)2=9zwl9AC0JGJ(-|Lz6QlHU2p1o4KwQ_rtZ))~@nR&-yapdO-oy)-TDVmFNy0W|X$!4(-Q}?c;cN%w= zGB)p%AyV(Dq$?OR(>&Uywv{$_Ga*_4{Olajl$S-!n3myh63l7sx=AX|e%{3gs%4aS z*E9P?YNVz5K2>73NS(}dmW9SU>kmzh>ST^M{oQ0}@cxA}Kj&NQzKi9X@P%-{3V>+g z*&US(5jAb|>KMP%qQMkv*J!gZKaF(ch|XT*=mTJya;}A1R#%zACV|%Rp5R&{SS!B z4SDSMmxP@(a@jfa>b1C=EAjG~LH_O$N<%{w>*+izaavRNJB&J5F|Rrj3XX!>0b*Ls z$UNR-yMgMV8BE0&pIe5OFoCG*cJ6Ufu1YM+CVj%Z2-ivSrLzp}I3}ThoP#b-P8)jyw)$uSukvrg2@rwxJ=r zZFEY4k2^ajv0NIKY;k?@VKd%@=fn6m>iAJvrmqdnvMZV*=b^KNuHdQM=H~@oZWCP8 zQR}~cz_+Q_;|Kl0-qe(7g>mT(1aPhJCx=ti4XWFf#97jKC>n1nW!F^tzSbMGSs&lq z>KX|dKcmr-X#&Y|a=-eKp9#V$0XOsN#+FttcTW}CsDBGF+}~qUFxWM}VYXxn&Sm!G zO$PC|Yu20zen9#A{Y*+rjolu7+(-nnNjmlU{aXIAFlR$_!HnyL+xYo+v=zL@5+l9}d;;-`W6%l*G_ zi(I;bYE1XzI*Jz%rpk^{m{jq4EyjRKRipyM^J`C;=*NnP2(wI@m! z|I&Z$8hYj9K8V}}LPlnY!zMvX%TLUT@XdL93c9UE`AoyPE3ivF55GvM=yi80xR`Q| zKg(?%yKeBza6h>e_uXSO_C|ckT#T*9Kv&38g1qj^64%t2PmCF-{U70^<)<9O*UtP# znMSuv1-KRCL>qEjku$%HdxT@rPrbBnti=9H&!bgt^($)Oz)hPqOL%TEb=!Wv`qiUA z(qinq-ROw~NfEwyE($gk7JJH_f2*hlFDx`Z*v%@0H|IwDo>Pm;gixmelW!+Sd#Wrc z1M@{TQaQp^*wy!oETd{dK5>JA2E$1;h8jI`Prq38C#>OGd~%|&N2p;20a0PpIFmbI6h9ry|{-s2|Et3oX0GKYLDG&Q(0nyf@P zzjp5lta4W4N{W77f0l~ppZ1OzOf}Sdr*z7|GUcYc+^U86?-(Gl_SMst5d6__eNjw( zQoq-W`a=&nh|=NLY}$oWDMB;q`nZ-hti%=lj}l5)Rj)S6@b`vhkFmvlbJa(wCmuo0 zuCF*n%5$5)g4TDTgkZxs*^J;>MV&BS@#@gX5MBY93%3aBZl8fkxsfb$%g36MnrGb0Q&s|@OYASy5!nY?S!vtN*bK$>v!7L_v@{0i zZhp?(mv9C<2`;r$*U7Z6%Z1I)pRZ|~i-sGtg=MW3)|ieK`v{z#y)>%W_2q9(4f*Q^ zR?8?~<-k~Q)=EEjEOvd3nN>Q$Q%cpRM4_2eGo6xFG~OJb+K2heXar)}4Nb~{OSd?}sbA*)1HIYu|Ofcf_^QJn!`+>q9i zJ;VXCyf!5Dn*C@h<=UFMKH0>ULdWt-eX=I&>JNXxjw&uxG<$rPsEjq-q|#8`yZ}1@ zXpMDbKYIvGnop99DF>3im`p2F-74&Ma~xmz#>_VLG4OqDfl}QmjTWCyef)5!es)~> zl$80G{w|p*pWaT#=vg+ymH3jgMV@OtZ@K<+;M)%l7ACe7o_jZ0& zX!&tME7Oky>Gg<(XBEo={j|#Tbj)~Il<|)_ zlt-JYo8QRI#LrW;Q+yQ8>V2G2yyJ1a> z98_s}{QVW!sqy#_!=~rwclY^5{PCC+jMeh;*_swehJnO1(|tUmQSubW_V5`=Pd}a&XFa#aHv{?3YH<72 z99^+EE6%GIHCNPNE{}_=Jgz_dPx<6prfRI#Z{a>^^g8{Nz-E#w%MOa)dRG&uR`}bi8j;C-;DQVHQXvM z4WpqJRUM~q-S($hV%(R7shFjT-+ALl=@1EIj_}j!Is}9i|4IgqguUwKlv>HEXo%Z3+b1jPkskdL8 z!CJ^{_M`Rt*ML%dYS+|WWd#7oBXme;C8M>hKpSH zbqLc^>b{TM$7GU#O;cQLnO~D9_J~!clUi;+-_L&8zs5~xnaQ&zHN!#n1_Wt>(%R5P zHm3D1(mVTk_1kyRwTYqCF^I{ea~$Kl$Bro0SKel`v-F!%s7kz~eD1phx8u>o`~EUo zfnZkjvya5vk#1H{b=6e^vh{~wS|p%tJAXZv@;F)LOzBVD-X%8YjPylch@VPJ2^;-% zq{iq(!&HT%#U{ML8H&c~llN#6J_4?+lDmhc+@O?qfTH>S428B6KOY_KuqplV=RAldx(C;#^ucFMJt*_wZhqjX}n+gKP|%d!a-yQyZx99xBl!|(wcUe z+;M|Cam%dJil^-;Lu70k)0Zj3!?Q7v6G<%d8CB&+h;>x0utz}E={}{Ks>sNs5vfAo zUumf^^6jdEj=6B=9={tgqjrC2wPa&Q=Wn)EmS;6J{T%D{9|tcNeekq1lrw+eePf3b z(k*(P-hNZOYKFFJf|M2!AzrI%HYDQ9aIxm1r83&*th3n>S#!F#I?k2$nWWWAY) zE3>1kZse=6qi1iM!%i*xcm9`?tCD%sBP)$-^~>&%Gg{@n9|k9;KZzG*0H9Z1uUjV& z;<~=2eZfYm+us%8WQ}Xf7owfYt;WY!$G)u$NOG$YVq0PYki>kq(Xz>!Y1osn=oJ}# z)%iP=5@>C81_P}X`Qj`@CI#I0vzrWGc1q+fmMy0@7s-s}bEZby=DVixQe4w!jP7r6 z)p}ra%5C#}r}~8p)^&BffeF3p@dFF1-=g4thdH`W8dkp#E?&GDrr(A}Jbs-vj$Iw9 zmykv9{!4IW)jH7^S@hs$!n*rQJGAmmLw@|BI}6*vM`K&iCdum$aes{P&xbA7>sdO- zYHY98(UEDt5JQ788T!dieK>7C=1&@f@Dk8+@0ZLIPx?F>oNvAoIk5S~6{?`!thM1$ zqrLuj4OToW9_WUcFJHU6AFLMogP_-}$KF4P z@R1TeyIJ}&GA;gqC9MiV?KwVSMRTjAPLs!ud&NkxcKLS+md~x%W-5#qo}rV1XUR4; zdYRB%0Ml&0$&1x}WtJog1x5*3iDLW9;J47S_8j?{3m@U^0Q*f@dLB8?HQbdPKl3-!&sb zE!m>5shKb+GjV6gmNIPfWc3SHByXI)n*Rn(sG(2$**2^TI6erXjDO@9T6zr@L6h)} zi~a#pPIHt<>SZC;DW3Zdz0oL=Q3Db0a*QQNu~|7QM|rdqv{ttFCf`n6)m_n*n5o65 z!mWZn6>sK_lR^`6Z!u6ZOoikg3IezaABDsCi}dE_vhbTlsr&DCBkiTeKzdt&p{K7; zS$ZdMsDDndc>ZQ+RYzKHCH}$QW~q8dt8`wLiw)^ccxR7o{`qu&NMD$Du-3<=`nHdr998hy#gNhN z%Wp-ZL9rN;H+#XgsA-+eZ{ZRNWbs!li;U+tVFFKUaIYwVPeO3xwf<->&N3|Y>9R)4 zp>%PlZz6=!<%f**y70VI+6|3xtB<0YMaaf=nU9t@e1OCb^yS=p`oUFX3b2xN9ebt8cIY)AiHDRV8!HqlsE3C?#=3VYqJu&&&^$c1J(hy|o{5^ylkoBVXIt!4&w{^xbMEqXA5? zc!^mEVN0E&?aS{X2<@Yh zBQvCfkQn*<@mxqj(}nfS*nF6>%e=!~XP@XhhVQ%V>(qy@n0GR=UMAg#TpD51xCel; z8%R#9$~u(u2b40Cef%U?@KixysHIoLVBSE%!0Nw!?5%>jvfAC(Mn2n+2ClE1Wh`z5 zItKodLn-nvx-`CbzIcfaH)at7E?1k#BAwU4?s|=?jk(oaY3E1^@9cgaeo$^$noi#{ zuWnIYz*yCy^)4v9sPrmy8&AO{&_;!nmhpADMiN7<*+t$V~VQKAV13BUt@x4Mj|9-%|j2xav<&dy(oY0`xDJ97S#BRp4f|w z-v^T0;Y6*P&IlG#vC zI`yDx4k^E~^BMG}Em1y)+aTHAddo8lRD6q5&EuHJ-j_NOhj9%U3?P@7ag*aC@la+ZWB(^d#yG(a&D8xE`Fic5~q9ghBSsmdZcdCj8l z&W){meDA~E>4F8ybxDaz8yt}jVS|a8+~xktL^OBjwweX>oI0WF-@`&YsPxMo?MMQ9 zG>sbud{5@zUm?V;X0tck?CG^OPnggBvY%ltoT}D~RzR;{9I4a=&_m+_w-Ror$O;)* zLGTNZ;%F&Q8q2A3Cizs@jOV*GEGduq1U_J_ul?Zgf8L0)(=HTubGuMYDd`l_eA*v= z9=~Iw(IPXpt)bsxJGpcur~CP^(pA^4G zM*eS<^1kcqYCoQ9C>abt&(R^bdZ9^ct_e6esoy64BfSBi_S|=*-9Sg##Vdj_kiFI8 zL4$QjRpU^Y+Uu=jDj@pZ@JVE@$$`{Mt{o_ICD$uCotP+Q+t)Q$lwG-Rel|Jl^;O{w zhmeN{TfZ(dERyJS5(S^3VbIcqQq>ElcFVIn&*x)>1q+_enQvOlw>PK#frClJZcM@k ze46!1AVN~#*pYUX_17`7XEN7Vi2iec?AXB^8z~k-)L>$zXKwk{{tC~pUcOCMG33*~ z!)gufNUYkrwPeGS1|uMD^rw^VhBpvGPTnvNsu*}`|#sA(jApehUR(VHj*Wq48CfcXG2;^ zrLweAj(vj~tqf{NtqWPWLUuV z?PPnnc86FlR!LEV5(KoXUMHwZHEd%BvDY}Y|B6n&Ua(-n*F9(6J`faA9CZsE3&<*A zdn>s0cTKE)U(&BZYV}2{ORCtAl9G^hV#Z&8WTx_ZK?YeG5#j!LE-+l#sYy2U-hSmN zpTHRJtVt!O!@ms-z9u%ga~mGiV1mvn6O5p3epUyqTZ7_}R@+8XD+xQFMo@rkS_1!U zQMMkS-B;yWa?lCueFF~D0+S#0@ z5r&rp@8>9qy z!c@$bHc*`;qSF4@SjU7)^e)%}f1SJ;RR2B*Kl`C952{u-1oG_-&!8n__F)KLd~_FO zwZ1Ryne#0R@}JvCI(c4tF7(dyHQ3BWS?qU@%hyXb2 zmCaJ+;pfvBf`zC?2`h$dnSq*lq3NjEQUN53yt*6d`t zS<|Yz=5L`yRG%IxUN_sE58)H->R#A@@V^b*=W12yzROyar%&FyxySSP$X-0#9LqMkJv0!!Cbz|ctVQD}TdMOk^xJ!a1( z01_G;?ThIO-Xb#ui8ruP;UomJoL89bw3YeAs%W7h+{c(x8|S!bCe? z8iqyb;#4ul7sG~M+MIklpewoi+SiPlGaGYn^ZCziYt)ehs%7QDbW<@ELd8x{;RFZs zgBTqfiet$U{7XMy-q{=}E7HDu3UKyK-~r7_X2IWn{eM0%;ypF+O{9a)%m^L^sas6TmWSh(V1Ys|cVSe2k`qGZ3wD zTxKOsctj4iVR$pQ0y2s5VFu6GZvKgw6heHHA66pv*0t4Zr%Fv~vxlU^r8A^aw-Lrp z5*iT|E8=WSY8Evc)le#+S^~?V#0XQ#sKKDaWVd|^-yyh4(U#<%@H=<+WStqW`&7<= zCu2fvXFw8=jNU`}`qN{mx}sM%d$$+pOv4yN!qX#)x~wuR8VP^tI~Pg}^xA>)=7Q?Q zF}h}fsLpuC{|Utylvn%EQMy$FVY>JMr7_j5z?UhEyuGt_Ln)mSiseWwE2PXw zA(e2$c)ZdS3o=F^N5ehymtYGFT$vB5=Eq$~4;4~+{!|sGT6MTlC2pNo>8=^Cnp)5V z3$|hPp8cWA+E{NyssUY@b0Ly~)qvQ98DW`KlWonf6MX=nq6yXMGu6ujPVruV3Y_`m z$%I+(DI^`tX)x2R3n};zY;x5(vW`^hHza3w z?M2d)bPPFuPFBzT>F}Cb#TWCm0w2AlF@s3gSJaKUE6*4vy_SImw8B;{{DXuI5)!2F`aI8CYNU?H|kazyBv~qW`u}X%U_(Oo5GIrZ^s>P{CWmb8lW+WiN z|E`bjtGJVR^EZX7zNO3Q)qnn44ALGD(p)#y35(}!(G2=f%c>D*OSv|k4!@vD{Cqq( zyzWsFC59+&U+0}`;5PHF7$EXdjC6S0uLpo-WbrzcQ1WCvxgruPmQj)j-uq!hy?d% z1+6f15PT#(CWU#Im<3yoWtM)3aeZgR$%Yic0-r|{9NHanNk4c$`#hX5YtGS@Y%yn8 zY1iwI*JCTsE~8N>X6Z@k$=<3!QS9_2#?X6!fNnfP@WhCa$TD2u>#c}WcHv9t`b-c! zHMQ$}-fHH7pw1e*K?ltM3pUO^1%Z=xp}cjl_>h(;F!ByO4HcFA7%Q zjxv1)9ob*P;2g{~!iKZn#*rzkT!$nuS5zSZG5wO*vJS(( zV!Jo5$zH75+Cc9x-QT`8hhc$B>l7Hp$ZH^D7 z@9<8B8B&qc&J0cdwxh3eQh%GupA=-INvuV3^%>zHl5++MhuH(D0!0w6fsc@rV%87_ z&moNV%)d!C%CXsQCkx?efUlh}cDrv^uR)lSi~P^;8*%c&$3t{e5{hJ}BxSf@=a2dUs5KW(xI7gp%G?bsDB@sF8>GY8&K;;%Fh zly86a^R7}jxpcf}VzA~qLqz+o@lf`TUwpU8`@mxkBJOFG$SlHH?d<42Rs`-5XsW`x zE0_1+js4EWt!UuX%BSt>o%M3freXX#z5Q@hBd!Ij()53%e+nd)wq2veEE={ zRSPiH6Td+Y~9cwGy`#c`Kn&|EEYV9z8vQv%Dhl|GfPewc+Tk-1rm@5LPqLTWF=Ov zk@Rq)OzrCOQbYfuIT=EiXO|`a9XZ{KHuW(-aZ8NiNYRA+7$bj1^9VE87A*9J<&+R` z&e`+nphEZFFs4Mk1bu7GVanfVlAShvIrYs2wLFPUc&@Hwo;1Y}DdY0%kl|@+j#BF_ zJSPqD((zaE=W_-OC5?EEDS^YrjBp__#HO;}8RV}OB{miRY15K+hCY{u@_v2m({1av z7HM^P#G$&NNXNm>$e>Xn*`b@CN_Kd@k?HOw3tEG2uXr6hrJwyxEsQLXkuxE`F?PC7 zM$i_{2ItW(bn@m>xJ=@r8h6%aB*v8-{nyP$QyI^Fp@DOL{KXO-&5HkS}CZc=$!cNxAWrq73t-rBcQ)y z<*U>eH_*};Y?Eu%#>P%;b5#&f}17NkWG^Yn*tRib%nujW5hrmDN0VmXROKa1!oOfkeV@%W4(xpoO$ zdX-Ki0FIbBOlsBkfWI~CkoeE1Mgw{X14?{B4kZ<9 z@j}HT*^f`|O|Dp)N)Ngnhw&+VGdGaBtatmrt((Y5r^0_eS>Lgq)aW1tf3NDLmMYTS zeZ(h@<9i$!WRybOP0c26#zyusEkt)3-s{yA+oVxZCF*`Kj02bIo3~ zEE&ES;bQO?FKA2IGQA&^>$G5Yu9*Q3GJe|!z(boU)5HDwG!HgZ!{8Ul&FycN3>Rc+ z(3kDIT#JFc^Cb=zS=>Bp$ad1@dEM&zs>mAI{5(+Obj_!$L(fGG^<7E!sZ~b-lwuHk9}gub zB;ktF9=-xSidu25i9%Q%RY&~H%YWyDRa>O5Mp6<}QQI0YT(Lie=WkV$e!I|k>T4+c zS;6b*GkqBh;S{-9L=TDiuC|f|$EZ)`#BP$&HBU;A%U~tem1yIli*(BNqkru5uL_rS z6GVj_Pna9ism(qUu;oNj>$_dGGnf8WQ>e}@K|lG5jGUFoA=SzB; z?VoDu_d5DJLL}lYJ$HGZkStV@?Vc;pcXm>LIMxAzU3^TPLEEq$#nuN{Q#p5#P|kY} zHDh@)4~QaoJ|JMh)8yOtdI((sP!Wxotp0JRa;mMPx#F7=J%qcU0$0r{xCT|g{{lFo zk&Z#Srax3LB`owiPn7 zPX-luSnx}s24x23W#_up)y9+flwz*t-%XAPQSv;*SV#m9h5k_8E?;Of@worFyZUwD z6(ZCTr524fCFV3Fs82|CPWpZWRK6j*8V#Fha+D*sGKC&`cz=Qr&mlCbrC@Z4jLE*( zRjqlkNFKu&*I81V^?hlbW7rM^3?fdp1^m9JP}-qMP_f^&Nx99?fe?n2h#Hz zvt~>$1y|24I@nGwl??6LEPeChwdWsd6&pSY&u^vJCS&`i>b@^Z8U9OgygWFYY?u(S zrzac6Sq$C>f^M@=pUALiDfR> z>>f_Nb&~_S&4yt1TNZPDF{FA#&0Vf2cswe!*QEV|6gT>(@GRNJ%Mpiz^Fjs?y>FK~ zo{yP2b0Q;|KBU|zWK&MVZZs)`_}^Gt{?UMS5v@e-Bt5*ZrcgIleMKa-qul6NOR(|e zumm03A=R9I^mnokZ;2xdq;3&9fRpj-FBJ{DbPE~O-@=s=&B?Qd7;y2x!!k*%ogaNT;l`8LVg)|A%kA|4V|mxMX3xse_dB%x z?NeNt_o}Wpw^%~AY(C&M{@I?6a<`$`3p>NHKtnX=N@G4gXV5SCa0$1qt8)? zc!A_&_O$lsgh0O35t;!UsJ+Lr}O_496lX@v7K8jmyEtn7nP``sRAr+B&!rG&g_j(#=)w z-+XDb;Wf?nq$F}ILb!3=A^oj6FWfv6Zx=0>xE7rEItNkdHt%>#_i`X_v3;_x36<<4 z3Z_3;brkK|dW@FSol?dV`Z-n<6C<8_GiGo;Z>%zD+eGezivzs@-F<3>7qy-+u$8zU zFd(zcBWnIdm^@xqVJfi;g3P+={ zyu((+FVfT5(F{J-r73qw7ch}+YPok`Ny{Njg_C#1{@gpm#td2XKk?wU$VeJBucGq; zC&wnD8a1$X)q|A&uO#wLEg5%PMK@*{A>E5g{hXwgRj8Ir%vU?Im?B(d*I4D}oOSe<)CMM5>nb zNS5T%?vDePlLa4KzV5JQnat2E2TRVyjI5sO(DcQ7UA${JfNA=OLBQ+M*;~5=a&tjq z>x~+mnbh1-=Py%ndT8zk!CTw@yoP;pmm9w{8F@i4bw#jVgypqHZBv%>^0kl$-{k0e z*wwh;sobSW%-EGub<%Rtm+GY`x%#Z<_w*RRIwgmr+OPQ+6u#kYL^X?rBcwVryS^ky zePVC7^l}*HE!T@taeMU7->B9>oa)3fRR%C1uU`feKGttz%AQx8m>Q)XO}gzk?9irC zP)2OLK(Yi?;A#HlP$u8Cyf4-M_XjYk4k03$|Go&&5^e2B0eSOGy%Z38eevvtcYKfAfTC32W&F;m5w*H(`?Sd~npU7da$f7&65TcdI6T8b| z75h`UFNTpzvo~)Nyb(D&o8S;@eb?V3?qdCoh>QF3ov%0DPY5sz2&C4!#?9@Ks+s$D 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 TbIqkK$j>yReLGWsWt{szR65>6 literal 0 HcmV?d00001 diff --git a/public/img/data-report.svg b/public/img/data-report.svg deleted file mode 100644 index 8ec416796c54..000000000000 --- a/public/img/data-report.svg +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/images/datto.png b/public/img/datto.png similarity index 100% rename from src/assets/images/datto.png rename to public/img/datto.png diff --git a/src/assets/images/huntress_teal.png b/public/img/huntress_teal.png similarity index 100% rename from src/assets/images/huntress_teal.png rename to public/img/huntress_teal.png diff --git a/src/assets/images/netfriends.png b/public/img/netfriends.png similarity index 100% rename from src/assets/images/netfriends.png rename to public/img/netfriends.png diff --git a/src/assets/images/RGB_Net_Friends_Logo_White_Color_Icon.png b/public/img/netfriends_dark.png similarity index 100% rename from src/assets/images/RGB_Net_Friends_Logo_White_Color_Icon.png rename to public/img/netfriends_dark.png diff --git a/src/assets/images/ninjaone.png b/public/img/ninjaone.png similarity index 100% rename from src/assets/images/ninjaone.png rename to public/img/ninjaone.png diff --git a/public/img/ninjaone_dark.png b/public/img/ninjaone_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..47bcc219ead8d9096a00e3aec525379f9d70c2f3 GIT binary patch literal 14381 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/rewst.png b/public/img/rewst.png similarity index 100% rename from src/assets/images/rewst.png rename to public/img/rewst.png diff --git a/public/img/rewst_dark.png b/public/img/rewst_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b215b8ec4632c5d5f724e4862f1b48280e4870 GIT binary patch literal 14869 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 z

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 Hu0mjfO=;&+ literal 0 HcmV?d00001 diff --git a/src/components/layout/AppFooter.jsx b/src/components/layout/AppFooter.jsx index 1830b0201999..2c27efe8efd3 100644 --- a/src/components/layout/AppFooter.jsx +++ b/src/components/layout/AppFooter.jsx @@ -1,32 +1,40 @@ import React from 'react' import { CFooter, CImage, CLink } from '@coreui/react' import { Link } from 'react-router-dom' -import huntressLogo from 'src/assets/images/huntress_teal.png' -import dattoLogo from 'src/assets/images/datto.png' -import rewstLogo from 'src/assets/images/rewst.png' -import netfriends from 'src/assets/images/netfriends.png' -import ninjaLogo from 'src/assets/images/ninjaone.png' -//todo: Add darkmode detection and change logos accordingly. +import { useSelector } from 'react-redux' +import { useMediaPredicate } from 'react-media-hook' + const AppFooter = () => { + const currentTheme = useSelector((state) => state.app.currentTheme) + const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' + const isDark = + currentTheme === 'impact' || (currentTheme === 'default' && preferredTheme === 'impact') + + const netfriends = isDark ? '/img/netfriends_dark.png' : '/img/netfriends.png' + const datto = isDark ? '/img/datto.png' : '/img/datto.png' + const huntress = isDark ? '/img/huntress_teal.png' : '/img/huntress_teal.png' + const rewst = isDark ? '/img/rewst_dark.png' : '/img/rewst.png' + const ninjaone = isDark ? '/img/ninjaone_dark.png' : '/img/ninjaone.png' + return (

- This application is sponsored by{' '} - - - {' '} - - - {' '} - - - {' '} - + This application is sponsored by + + + + + + + + + + - - + +

From 05918cddbeaf8554cbbeff823e7ffc48e9632cb0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 13 Dec 2023 08:05:16 -0500 Subject: [PATCH 05/51] Tenant Onboarding Wizard Extend CippWizard - add hideSubmit flag Extend WizardTableField - allow for additional CippDatatable properties --- src/_nav.jsx | 5 + src/adminRoutes.js | 8 + src/components/layout/CippWizard.jsx | 6 +- src/components/tables/WizardTableField.jsx | 5 +- .../administration/TenantOnboardingWizard.jsx | 402 ++++++++++++++++++ 5 files changed, 423 insertions(+), 3 deletions(-) create mode 100644 src/views/tenant/administration/TenantOnboardingWizard.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index fc47f52686b5..1202c3838a9a 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -142,6 +142,11 @@ const _nav = [ name: 'App Consent Requests', to: '/tenant/administration/app-consent-requests', }, + { + component: CNavItem, + name: 'Tenant Onboarding', + to: '/tenant/administration/tenant-onboarding-wizard', + }, { component: CNavItem, name: 'Tenant Offboarding', diff --git a/src/adminRoutes.js b/src/adminRoutes.js index 5192ca20a203..ece783822eff 100644 --- a/src/adminRoutes.js +++ b/src/adminRoutes.js @@ -14,6 +14,9 @@ const appapproval = React.lazy(() => import('src/views/cipp/AppApproval')) const TenantOffboardingWizard = React.lazy(() => import('src/views/tenant/administration/TenantOffboardingWizard'), ) +const TenantOnboardingWizard = React.lazy(() => + import('src/views/tenant/administration/TenantOnboardingWizard'), +) const adminRoutes = [ { path: '/cipp', name: 'CIPP' }, @@ -46,6 +49,11 @@ const adminRoutes = [ name: 'Tenant Offboarding', component: TenantOffboardingWizard, }, + { + path: '/tenant/administration/tenant-onboarding-wizard', + name: 'Tenant Onboarding', + component: TenantOnboardingWizard, + }, ] export default adminRoutes diff --git a/src/components/layout/CippWizard.jsx b/src/components/layout/CippWizard.jsx index 02c4220b48a4..9ebfc1097fae 100644 --- a/src/components/layout/CippWizard.jsx +++ b/src/components/layout/CippWizard.jsx @@ -13,6 +13,7 @@ export default class CippWizard extends React.Component { onPageChange: PropTypes.func, nextPage: PropTypes.func, previousPage: PropTypes.func, + hideSubmit: PropTypes.bool, } static defaultProps = { @@ -27,6 +28,7 @@ export default class CippWizard extends React.Component { page: 0, values: props.initialValues, wizardTitle: props.wizardTitle, + hideSubmit: props.hideSubmit, } } @@ -64,7 +66,7 @@ export default class CippWizard extends React.Component { render() { const { children } = this.props - const { page, values, wizardTitle } = this.state + const { page, values, wizardTitle, hideSubmit } = this.state const activePage = React.Children.toArray(children)[page] const isLastPage = page === React.Children.count(children) - 1 @@ -104,7 +106,7 @@ export default class CippWizard extends React.Component { Next » )} - {isLastPage && ( + {isLastPage && !hideSubmit && ( <> Submit diff --git a/src/components/tables/WizardTableField.jsx b/src/components/tables/WizardTableField.jsx index 3e61e1e4d1a8..bb6ba35a9da1 100644 --- a/src/components/tables/WizardTableField.jsx +++ b/src/components/tables/WizardTableField.jsx @@ -13,6 +13,7 @@ export default class WizardTableField extends React.Component { reportName: PropTypes.string.isRequired, keyField: PropTypes.string.isRequired, path: PropTypes.string.isRequired, + params: PropTypes.object, columns: PropTypes.array.isRequired, fieldProps: PropTypes.object, } @@ -56,7 +57,7 @@ export default class WizardTableField extends React.Component { } render() { - const { reportName, keyField, columns, path } = this.props + const { reportName, keyField, columns, path, params, ...props } = this.props return ( ) } diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx new file mode 100644 index 000000000000..879b2d8328b3 --- /dev/null +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -0,0 +1,402 @@ +import React, { useState, useRef, useEffect } from 'react' +import { + CAccordion, + CAccordionBody, + CAccordionHeader, + CAccordionItem, + CButton, + CCallout, + CCol, + CFormLabel, + CListGroup, + CListGroupItem, + CRow, + CSpinner, +} from '@coreui/react' +import { Field, FormSpy } from 'react-final-form' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons' +import { useSelector } from 'react-redux' +import { CippWizard } from 'src/components/layout' +import PropTypes from 'prop-types' +import { RFFCFormCheck, RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms' +import { CippCodeBlock, TenantSelector } from 'src/components/utilities' +import { useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { + CellDate, + WizardTableField, + cellDateFormatter, + cellNullTextFormatter, +} from 'src/components/tables' +import ReactTimeAgo from 'react-time-ago' + +const Error = ({ name }) => ( + + touched && error ? ( + + + {error} + + ) : null + } + /> +) + +Error.propTypes = { + name: PropTypes.string.isRequired, +} + +function useInterval(callback, delay, state) { + const savedCallback = useRef() + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback + }) + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current() + } + + if (delay !== null) { + let id = setInterval(tick, delay) + return () => clearInterval(id) + } + }, [delay, state]) +} + +const RelationshipOnboarding = ({ relationship, gdapRoles }) => { + const [relationshipReady, setRelationshipReady] = useState(false) + const [refreshGuid, setRefreshGuid] = useState(false) + const [getOnboardingStatus, onboardingStatus] = useLazyGenericPostRequestQuery() + var headerIcon = relationshipReady ? 'check-circle' : 'question-circle' + + useInterval( + async () => { + if (onboardingStatus.data?.Status == 'running' || onboardingStatus.data?.Status == 'queued') { + getOnboardingStatus({ + path: '/api/ExecOnboardTenant', + values: { id: relationship.id }, + }) + } + }, + 5000, + onboardingStatus.data, + ) + + return ( + + + {onboardingStatus?.data?.Status == 'running' ? ( + + ) : ( + + )} + Onboarding Relationship: {} + {relationship.displayName} + + + + {(relationship?.customer?.displayName || + onboardingStatus?.data?.Relationship?.customer?.displayName) && ( + +

Customer

+ {onboardingStatus?.data?.Relationship?.customer?.displayName + ? onboardingStatus?.data?.Relationship?.customer?.displayName + : relationship.customer.displayName} +
+ )} + {onboardingStatus?.data?.Timestamp && ( + +

Last Updated

+ +
+ )} + +

Relationship Status

+ {relationship.status} +
+ +

Creation Date

+ +
+ {relationship.status == 'approvalPending' && + onboardingStatus?.data?.Relationship?.status != 'active' && ( + +

Invite URL

+ +
+ )} +
+ {onboardingStatus.isUninitialized && + getOnboardingStatus({ + path: '/api/ExecOnboardTenant', + values: { id: relationship.id, gdapRoles }, + })} + {onboardingStatus.isSuccess && ( + <> + {onboardingStatus.data?.Status == 'failed' && ( + + getOnboardingStatus({ + path: '/api/ExecOnboardTenant?Retry=True', + values: { id: relationship.id, gdapRoles }, + }) + } + className="mb-3" + > + Retry + + )} +
+ {onboardingStatus.data?.OnboardingSteps?.map((step, idx) => ( + + + {step.Status == 'running' ? ( + + ) : ( + + )}{' '} + {step.Title} + + + {step.Message} + + + ))} + + )} +
+
+ ) +} + +const TenantOnboardingWizard = () => { + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const currentSettings = useSelector((state) => state.app) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + + const handleSubmit = async (values) => {} + const columns = [ + { + name: 'Tenant', + selector: (row) => row.customer?.displayName, + sortable: true, + exportSelector: 'customer/displayName', + cell: cellNullTextFormatter(), + }, + { + name: 'Relationship Name', + selector: (row) => row['displayName'], + sortable: true, + exportSelector: 'displayName', + }, + { + name: 'Status', + selector: (row) => row['status'], + sortable: true, + exportSelector: 'status', + }, + { + name: 'Created', + selector: (row) => row['createdDateTime'], + sortable: true, + exportSelector: 'createdDateTime', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Activated', + selector: (row) => row['activatedDateTime'], + sortable: true, + exportSelector: 'activatedDateTime', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'End', + selector: (row) => row['endDateTime'], + sortable: true, + exportSelector: 'endDateTime', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Auto Extend', + selector: (row) => row['autoExtendDuration'], + sortable: true, + exportSelector: 'endDateTime', + cell: (row) => (row['autoExtendDuration'] === 'PT0S' ? 'No' : 'Yes'), + }, + { + name: 'Includes CA Role', + selector: (row) => row?.accessDetails, + sortable: true, + cell: (row) => + row?.accessDetails?.unifiedRoles?.filter( + (e) => e.roleDefinitionId === '62e90394-69f5-4237-9190-012177145e10', + ).length > 0 + ? 'Yes' + : 'No', + }, + ] + return ( + + +
+

Step 1

+
Choose a relationship
+
+
+ + {(props) => ( + + )} + +
+
+ +
+

Step 2

+
Tenant Onboarding Options
+
+
+ + (Optional) Automatically map groups for relationships not created in CIPP. This will not + map groups that do not have a corresponding role in the relationship. + + + {(props) => ( + row['RoleName'], + sortable: true, + exportselector: 'Name', + }, + { + name: 'Group', + selector: (row) => row['GroupName'], + sortable: true, + }, + ]} + fieldProps={props} + /> + )} + +
+
+ +
+

Step 3

+
Tenant Onboarding
+
+
+
+ + {/* eslint-disable react/prop-types */} + {(props) => { + return ( + <> + + + +
Onboarding Status
+ + {props.values.selectedRelationships.map((relationship, idx) => ( + + ))} + +
+
+ + ) + }} +
+
+
+
+
+ ) +} + +export default TenantOnboardingWizard From d3cea9936ba50bb62d67d9aba32346398f7f2b41 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 13 Dec 2023 23:41:46 +0100 Subject: [PATCH 06/51] Logo replacement, redo CSS --- src/assets/images/CIPP.png | Bin 16268 -> 38867 bytes src/components/layout/AppHeader.jsx | 52 ++++++------------ src/components/layout/AppSidebar.jsx | 27 +++++++-- .../utilities/CippActionsOffcanvas.jsx | 13 ++++- src/layout/DefaultLayout.jsx | 3 +- src/scss/_custom.scss | 17 +----- src/scss/_themes.scss | 2 +- src/scss/_variables.scss | 2 +- 8 files changed, 56 insertions(+), 60 deletions(-) diff --git a/src/assets/images/CIPP.png b/src/assets/images/CIPP.png index c3cec15dd468f28869357a352f0417c5a804dfda..cf279633a13f4f052daf384b719975cd3570f026 100644 GIT binary patch literal 38867 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 diff --git a/src/components/layout/AppHeader.jsx b/src/components/layout/AppHeader.jsx index 9dcc3bd54c2c..1e425ff93317 100644 --- a/src/components/layout/AppHeader.jsx +++ b/src/components/layout/AppHeader.jsx @@ -20,7 +20,13 @@ import cyberdrainlogolight from 'src/assets/images/CIPP.png' import cyberdrainlogodark from 'src/assets/images/CIPP_Dark.png' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCaretSquareLeft, faCaretSquareRight } from '@fortawesome/free-solid-svg-icons' +import { + faBars, + faCaretSquareLeft, + faCaretSquareRight, + faHamburger, + faStroopwafel, +} from '@fortawesome/free-solid-svg-icons' import { setCurrentTheme, setUserSettings, toggleSidebarShow } from 'src/store/features/app' import { useMediaPredicate } from 'react-media-hook' import { useGenericGetRequestQuery, useLoadAlertsDashQuery } from 'src/store/api/app' @@ -75,29 +81,15 @@ const AppHeader = () => { return ( <> - - - - dispatch(toggleSidebarShow({ sidebarShow }))} - > - - - - + + dispatch(toggleSidebarShow({ sidebarShow }))} + style={{ marginInlineStart: '-50x' }} + > + + + @@ -144,16 +136,8 @@ const AppHeader = () => { {dashboard && dashboard.length >= 1 && dashboard.map((item, index) => ( -

- +
+ {item.Alert} Link
diff --git a/src/components/layout/AppSidebar.jsx b/src/components/layout/AppSidebar.jsx index 980832641a71..aad3a2eb170d 100644 --- a/src/components/layout/AppSidebar.jsx +++ b/src/components/layout/AppSidebar.jsx @@ -1,11 +1,20 @@ import React from 'react' import { useSelector, useDispatch } from 'react-redux' -import { CSidebar, CSidebarNav } from '@coreui/react' +import { + CCloseButton, + CHeaderNav, + CImage, + CSidebar, + CSidebarBrand, + CSidebarHeader, + CSidebarNav, +} from '@coreui/react' import { AppSidebarNav } from 'src/components/layout' import SimpleBar from 'simplebar-react' import 'simplebar/dist/simplebar.min.css' import navigation from 'src/_nav' import { setSidebarVisible } from 'src/store/features/app' +import cyberdrainlogolight from 'src/assets/images/CIPP.png' const AppSidebar = () => { const dispatch = useDispatch() @@ -14,13 +23,21 @@ const AppSidebar = () => { return ( { - dispatch(setSidebarVisible({ visible })) + dispatch({ type: 'set', sidebarShow: visible }) }} + position="fixed" + unfoldable={false} + visible={sidebarShow} > + + + + + dispatch({ type: 'set', sidebarShow: false })} + /> diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index 391ae9365d9a..fb378db83f8e 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -5,6 +5,7 @@ import { CCallout, CCard, CCardBody, + CCardHeader, CCardText, CCardTitle, CFormInput, @@ -21,6 +22,8 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 's import { Link, useNavigate } from 'react-router-dom' import { stringCamelCase } from 'src/components/utilities/CippCamelCase' import ReactTimeAgo from 'react-time-ago' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faGlobe } from '@fortawesome/free-solid-svg-icons' export default function CippActionsOffcanvas(props) { const inputRef = useRef('') @@ -292,9 +295,15 @@ export default function CippActionsOffcanvas(props) { Could not connect to API: {getResults.error.message} )} - Extended Information + + + + Extended Information + + + {extendedInfoContent} + {cardContent && cardContent} - {extendedInfoContent} {Actions} {actionsContent} diff --git a/src/layout/DefaultLayout.jsx b/src/layout/DefaultLayout.jsx index f5721117185f..07ec1d0ed228 100644 --- a/src/layout/DefaultLayout.jsx +++ b/src/layout/DefaultLayout.jsx @@ -51,9 +51,10 @@ const DefaultLayout = () => { - +
+
}> diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss index 4404602e90a4..0a5568b7126c 100644 --- a/src/scss/_custom.scss +++ b/src/scss/_custom.scss @@ -544,8 +544,6 @@ h3.underline:after { .wrapper.d-flex.flex-column.min-vh-100 { background-color: var(--cui-color-gray-1) !important; - padding-top: 30px; - min-height: calc(100vh - 84px) !important; z-index: 2; // .body.flex-grow-1.px-xl-3>.container-fluid>div>div{ @@ -641,16 +639,10 @@ h3.underline:after { .sidebar-brand { background: none; .sidebar-brand-full { - margin-left: 30px; - height: 50px; + margin: 15px; transform: scale(1.5); } } - -.sidebar.sidebar-fixed { - margin-top: 69px; - height: calc(100vh - 69px); -} .wrapper.d-flex.flex-column.min-vh-100 .card { margin-bottom: 0px; } @@ -706,10 +698,3 @@ i.glyphicon { .array-item-remove::after { content: 'Remove'; } - -.stickyfooter { - position: sticky; - bottom: 0; - z-index: 0; /* Adjust this value as needed, should be lower than dropdowns */ - /* Other styling as needed */ -} diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss index dbd119303b6b..b427f810d76a 100644 --- a/src/scss/_themes.scss +++ b/src/scss/_themes.scss @@ -588,7 +588,7 @@ background-color: var(--cui-bgcolor-table-header); } .table { - --cui-table-bg: var(--cyberdrain-dark); + --cui-table-bg: rgba(40, 40, 40, 0.8); --cui-table-color: var(--cyberdrain-light); --cui-table-striped-bg: var(--cyberdrain-dark-striped); --cui-table-striped-color: var(--cyberdrain-light-striped); diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index d9013e625c45..7d77d8000e3b 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -1739,7 +1739,7 @@ $sidebar-nav-link-active-icon-color: rgba(247, 127, 0) !default; // Footer // scss-docs-start footer-variables -// $footer-min-height: 3rem !default; +$footer-min-height: 4rem !default; // $footer-padding-y: $spacer / 2 !default; // $footer-padding-x: $spacer !default; // $footer-bg: $gray-100 !default; From a0bc505e88435c92401b9c18b794513030a9a1ae Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 14 Dec 2023 13:38:35 +0100 Subject: [PATCH 07/51] added div for when no alert has popped --- src/components/layout/AppHeader.jsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/layout/AppHeader.jsx b/src/components/layout/AppHeader.jsx index 1e425ff93317..3ceb14542dff 100644 --- a/src/components/layout/AppHeader.jsx +++ b/src/components/layout/AppHeader.jsx @@ -132,16 +132,17 @@ const AppHeader = () => { - - {dashboard && - dashboard.length >= 1 && - dashboard.map((item, index) => ( -
- - {item.Alert} Link - -
- ))} +
+ {dashboard && + dashboard.length >= 1 && + dashboard.map((item, index) => ( +
+ + {item.Alert} Link + +
+ ))} +
) } From f6546e47c70fc6126922f8fc6802662cbc657ab2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Dec 2023 22:09:01 -0500 Subject: [PATCH 08/51] Tenant Onboarding Add modal for logs Add toggles for mapping roles and adding SAM user to missing groups --- .../administration/TenantOnboardingWizard.jsx | 166 ++++++++++-------- 1 file changed, 96 insertions(+), 70 deletions(-) diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index 879b2d8328b3..223d91569fbd 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -7,9 +7,6 @@ import { CButton, CCallout, CCol, - CFormLabel, - CListGroup, - CListGroupItem, CRow, CSpinner, } from '@coreui/react' @@ -29,6 +26,7 @@ import { cellNullTextFormatter, } from 'src/components/tables' import ReactTimeAgo from 'react-time-ago' +import { TableModalButton } from 'src/components/buttons' const Error = ({ name }) => ( ( render={({ meta: { touched, error } }) => touched && error ? ( - + {error} ) : null @@ -70,7 +68,7 @@ function useInterval(callback, delay, state) { }, [delay, state]) } -const RelationshipOnboarding = ({ relationship, gdapRoles }) => { +const RelationshipOnboarding = ({ relationship, gdapRoles, autoMapRoles, addMissingGroups }) => { const [relationshipReady, setRelationshipReady] = useState(false) const [refreshGuid, setRefreshGuid] = useState(false) const [getOnboardingStatus, onboardingStatus] = useLazyGenericPostRequestQuery() @@ -155,22 +153,26 @@ const RelationshipOnboarding = ({ relationship, gdapRoles }) => { {onboardingStatus.isUninitialized && getOnboardingStatus({ path: '/api/ExecOnboardTenant', - values: { id: relationship.id, gdapRoles }, + values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups }, })} {onboardingStatus.isSuccess && ( <> - {onboardingStatus.data?.Status == 'failed' && ( - - getOnboardingStatus({ - path: '/api/ExecOnboardTenant?Retry=True', - values: { id: relationship.id, gdapRoles }, - }) - } - className="mb-3" - > - Retry - + {onboardingStatus.data?.Status != 'running' && + onboardingStatus.data?.Status != 'queued' && ( + + getOnboardingStatus({ + path: '/api/ExecOnboardTenant?Retry=True', + values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups }, + }) + } + className="mb-3 me-2" + > + Retry + + )} + {onboardingStatus.data?.Logs && ( + )}
{onboardingStatus.data?.OnboardingSteps?.map((step, idx) => ( @@ -211,6 +213,7 @@ const TenantOnboardingWizard = () => { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const currentSettings = useSelector((state) => state.app) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const requiredArray = (value) => (value && value.length !== 0 ? undefined : 'Required') const handleSubmit = async (values) => {} const columns = [ @@ -289,32 +292,38 @@ const TenantOnboardingWizard = () => {
Choose a relationship

- + {(props) => ( - + <> + + + )}
@@ -328,33 +337,48 @@ const TenantOnboardingWizard = () => {
Tenant Onboarding Options

- - (Optional) Automatically map groups for relationships not created in CIPP. This will not - map groups that do not have a corresponding role in the relationship. - - - {(props) => ( - row['RoleName'], - sortable: true, - exportselector: 'Name', - }, - { - name: 'Group', - selector: (row) => row['GroupName'], - sortable: true, - }, - ]} - fieldProps={props} - /> - )} - + + + + {/* eslint-disable react/prop-types */} + {(props) => { + return ( + <> + {(props.values.autoMapRoles || props.values.addMissingGroups) && ( + + {(props) => ( + <> + row['RoleName'], + sortable: true, + exportselector: 'Name', + }, + { + name: 'Group', + selector: (row) => row['GroupName'], + sortable: true, + }, + ]} + fieldProps={props} + /> + + + )} + + )} + + ) + }} +
{ ))} From bbc6a40fef5db95bf194b1b59d41d01137799612 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Dec 2023 22:27:35 -0500 Subject: [PATCH 09/51] Add icons to buttons --- src/components/buttons/TableModalButton.jsx | 11 ++++++++++- .../tenant/administration/TenantOnboardingWizard.jsx | 9 +++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/buttons/TableModalButton.jsx b/src/components/buttons/TableModalButton.jsx index 1fb7dbeb3ae7..67d2b22193da 100644 --- a/src/components/buttons/TableModalButton.jsx +++ b/src/components/buttons/TableModalButton.jsx @@ -3,8 +3,16 @@ import { CButton } from '@coreui/react' import { ModalService } from '../utilities' import { cellGenericFormatter } from '../tables/CellGenericFormat' import PropTypes from 'prop-types' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -export default function TableModalButton({ data, title, className, countOnly = false, ...input }) { +export default function TableModalButton({ + data, + title, + className, + countOnly = false, + icon = '', + ...input +}) { const handleTable = (data) => { const QueryColumns = [] const columns = Object.keys(data[0]).map((key) => { @@ -31,6 +39,7 @@ export default function TableModalButton({ data, title, className, countOnly = f return ( handleTable(data)}> + {icon != '' && } <>{countOnly === true ? data.length : `${title} (${data.length})`} ) diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index 223d91569fbd..21c3b406cbd6 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -168,11 +168,16 @@ const RelationshipOnboarding = ({ relationship, gdapRoles, autoMapRoles, addMiss } className="mb-3 me-2" > - Retry + Retry )} {onboardingStatus.data?.Logs && ( - + )}
{onboardingStatus.data?.OnboardingSteps?.map((step, idx) => ( From 70373cd4f2b743e96c73171b6d28dc339181804c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Dec 2023 22:49:41 -0500 Subject: [PATCH 10/51] Add onboarding button to invite Block filter on role table --- src/views/tenant/administration/GDAPInviteWizard.jsx | 9 +++++++++ .../tenant/administration/TenantOnboardingWizard.jsx | 1 + 2 files changed, 10 insertions(+) diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index 23fa60e962e6..e3c2ce2a1d81 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -87,6 +87,7 @@ const GDAPInviteWizard = () => {
+ {(props) => ( { wrapLongLines={true} language="text" /> + + ) })} diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index 21c3b406cbd6..2790f698812e 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -360,6 +360,7 @@ const TenantOnboardingWizard = () => { reportName="gdaproles" keyField="defaultDomainName" path="/api/ListGDAPRoles" + isModal={true} columns={[ { name: 'Name', From c8400a4cb8d9f32f3f97b2a951f443f739aaf0ef Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 17 Dec 2023 21:16:32 -0500 Subject: [PATCH 11/51] Add invite link --- src/views/tenant/administration/TenantOnboardingWizard.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index 2790f698812e..ff1d19a9f96d 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -26,7 +26,7 @@ import { cellNullTextFormatter, } from 'src/components/tables' import ReactTimeAgo from 'react-time-ago' -import { TableModalButton } from 'src/components/buttons' +import { TableModalButton, TitleButton } from 'src/components/buttons' const Error = ({ name }) => ( {
Choose a relationship

+
+ +
{(props) => ( <> From 5fa12c16a49ad8a90fd770bc6abd8884620de39b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 17 Dec 2023 21:20:41 -0500 Subject: [PATCH 12/51] Wording --- src/views/tenant/administration/TenantOnboardingWizard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index ff1d19a9f96d..e8f5b9344fc1 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -347,7 +347,7 @@ const TenantOnboardingWizard = () => {
From d7b8d17f1da78806c0598932fa8001621a4e5f0c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 17 Dec 2023 21:33:57 -0500 Subject: [PATCH 13/51] Update TenantOnboardingWizard.jsx --- src/views/tenant/administration/TenantOnboardingWizard.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index e8f5b9344fc1..bdd301663507 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -345,10 +345,7 @@ const TenantOnboardingWizard = () => {
Tenant Onboarding Options

- + {/* eslint-disable react/prop-types */} From 56adc4e389c9be075c94ff6be2614379c4da690e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 18 Dec 2023 11:17:36 +0100 Subject: [PATCH 14/51] remove bouncing issue --- src/scss/_custom.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss index 0a5568b7126c..60d5abcc0cdd 100644 --- a/src/scss/_custom.scss +++ b/src/scss/_custom.scss @@ -3,6 +3,9 @@ position: relative; display: inline-block; } +body { + overflow-y: scroll; // Always show vertical scrollbar +} .image-upload-container { position: relative; width: 100px; /* Adjusted to match the image */ @@ -543,9 +546,9 @@ h3.underline:after { } .wrapper.d-flex.flex-column.min-vh-100 { + //full width -20px for scrollbar bouncing background-color: var(--cui-color-gray-1) !important; z-index: 2; - // .body.flex-grow-1.px-xl-3>.container-fluid>div>div{ // background: var(--cui-color-white) !important; // padding: 20px 30px; From 46d476a4895964095f6bb0ec6dc54a47b2c1e286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 18 Dec 2023 22:26:11 +0100 Subject: [PATCH 15/51] Update standards.json with new authentication standards and reorder a bit --- src/data/standards.json | 165 +++++++++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 60 deletions(-) diff --git a/src/data/standards.json b/src/data/standards.json index b38bc7eafca9..51301804bb01 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -100,21 +100,12 @@ { "cat": "Entra (AAD) Standards", "name": "standards.allowOTPTokens", - "helpText": "Allows you to use any OTP token generator", + "helpText": "Allows you to use MS authenticator OTP token generator", "addedComponent": [], "label": "Enable OTP via Authenticator.", "impact": "Low Impact", "impactColour": "info" }, - { - "cat": "Entra (AAD) Standards", - "name": "standards.allowOAuthTokens", - "helpText": "Allows you to use any software OAuth token generator", - "addedComponent": [], - "label": "Enable OTP Software oAuth tokens.", - "impact": "Low Impact", - "impactColour": "info" - }, { "cat": "Entra (AAD) Standards", "name": "standards.PWcompanionAppAllowedState", @@ -140,6 +131,33 @@ "impact": "Low Impact", "impactColour": "info" }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.EnableFIDO2", + "helpText": "Enables the FIDO2 authenticationMethod for the tenant", + "addedComponent": [], + "label": "Enable FIDO2 capabilities", + "impact": "Low Impact", + "impactColour": "info" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.EnableHardwareOAuth", + "helpText": "Enables the HardwareOath authenticationMethod for the tenant. This allows you to use hardware tokens for generating 6 digit MFA codes.", + "addedComponent": [], + "label": "Enable Hardware OAuth tokens", + "impact": "Low Impact", + "impactColour": "info" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.allowOAuthTokens", + "helpText": "Allows you to use any software OAuth token generator", + "addedComponent": [], + "label": "Enable OTP Software OAuth tokens", + "impact": "Low Impact", + "impactColour": "info" + }, { "cat": "Entra (AAD) Standards", "name": "standards.TAP", @@ -225,15 +243,6 @@ "impact": "Low Impact", "impactColour": "info" }, - { - "cat": "Entra (AAD) Standards", - "name": "standards.EnableFIDO2", - "helpText": "Enables the FIDO2 authenticationMethod for the tenant", - "addedComponent": [], - "label": "Enable FIDO2 capabilities", - "impact": "Low Impact", - "impactColour": "info" - }, { "cat": "Entra (AAD) Standards", "name": "standards.DisableSecurityGroupUsers", @@ -270,29 +279,6 @@ "impact": "Medium Impact", "impactColour": "warning" }, - { - "cat": "Entra (AAD) Standards", - "name": "standards.SecurityDefaults", - "helpText": "Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access.", - "addedComponent": [], - "label": "Enable Security Defaults", - "impact": "High Impact", - "impactColour": "danger" - }, - { - "cat": "Entra (AAD) Standards", - "name": "standards.UndoOauth", - "disabledFeatures": { - "report": true, - "warn": true, - "remediate": false - }, - "helpText": "Disables App consent and set to Allow user consent for apps", - "addedComponent": [], - "label": "Undo App Consent Standard", - "impact": "High Impact", - "impactColour": "danger" - }, { "cat": "Entra (AAD) Standards", "name": "standards.OauthConsent", @@ -316,6 +302,65 @@ "impact": "Medium impact", "impactColour": "warning" }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.UndoOauth", + "disabledFeatures": { + "report": true, + "warn": true, + "remediate": false + }, + "helpText": "Disables App consent and set to Allow user consent for apps", + "addedComponent": [], + "label": "Undo App Consent Standard", + "impact": "High Impact", + "impactColour": "danger" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.SecurityDefaults", + "helpText": "Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access.", + "addedComponent": [], + "label": "Enable Security Defaults", + "impact": "High Impact", + "impactColour": "danger" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.DisableSMS", + "helpText": "This blocks users from using SMS as an MFA method. If a user only has SMS as a MFA method, they will be unable to login.", + "addedComponent": [], + "label": "Disables SMS as an MFA method", + "impact": "High Impact", + "impactColour": "danger" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.DisableVoice", + "helpText": "This blocks users from using Voice call as an MFA method. If a user only has Voice as a MFA method, they will be unable to login.", + "addedComponent": [], + "label": "Disables Voice call as an MFA method", + "impact": "High Impact", + "impactColour": "danger" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.DisableEmail", + "helpText": "This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead promts them to create a Microsoft account.", + "addedComponent": [], + "label": "Disables Email as an MFA method", + "impact": "High Impact", + "impactColour": "danger" + }, + { + "cat": "Entra (AAD) Standards", + "name": "standards.Disablex509Certificate", + "helpText": "This blocks users from using Certificates as an MFA method.", + "addedComponent": [], + "label": "Disables Certificates as an MFA method", + "impact": "High Impact", + "impactColour": "danger" + }, { "name": "standards.OutBoundSpamAlert", "cat": "Exchange Standards", @@ -615,6 +660,24 @@ "impact": "High Impact", "impactColour": "danger" }, + { + "name": "standards.DisableReshare", + "cat": "SharePoint Standards", + "helpText": "Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access", + "addedComponent": [], + "label": "Disable Resharing by External Users", + "impact": "High Impact", + "impactColour": "danger" + }, + { + "name": "standards.DisableUserSiteCreate", + "cat": "SharePoint Standards", + "helpText": "Disables users from creating new SharePoint sites", + "addedComponent": [], + "label": "Disable site creation by standard users", + "impact": "High Impact", + "impactColour": "danger" + }, { "name": "standards.ExcludedfileExt", "cat": "SharePoint Standards", @@ -639,24 +702,6 @@ "impact": "High Impact", "impactColour": "danger" }, - { - "name": "standards.DisableReshare", - "cat": "SharePoint Standards", - "helpText": "Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access", - "addedComponent": [], - "label": "Disable Resharing by External Users", - "impact": "High Impact", - "impactColour": "danger" - }, - { - "name": "standards.DisableUserSiteCreate", - "cat": "SharePoint Standards", - "helpText": "Disables users from creating new SharePoint sites", - "addedComponent": [], - "label": "Disable site creation by standard users", - "impact": "High Impact", - "impactColour": "danger" - }, { "name": "standards.unmanagedSync", "cat": "SharePoint Standards", From c8000c92ed9b3fd35379b3b7f496f6a53b321405 Mon Sep 17 00:00:00 2001 From: rvdwegen Date: Wed, 20 Dec 2023 08:23:28 +0100 Subject: [PATCH 16/51] Add vendor app removal to tenant offboarding --- src/data/vendorTenantList.json | 238 ++++++++++++++++++ .../TenantOffboardingWizard.jsx | 46 +++- 2 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 src/data/vendorTenantList.json diff --git a/src/data/vendorTenantList.json b/src/data/vendorTenantList.json new file mode 100644 index 000000000000..f6fac9ed08d4 --- /dev/null +++ b/src/data/vendorTenantList.json @@ -0,0 +1,238 @@ +[ + { + "vendorName": "Augmentt", + "vendorTenantId": "29f0611d-e8d3-4946-914c-677d2ad8065b" + }, + { + "vendorName": "Rewst", + "vendorTenantId": "5a9dda56-5beb-4840-83e1-9afc1ada2388" + }, + { + "vendorName": "Datto Backupify", + "vendorTenantId": "8ebde5a4-a587-497c-9881-8a5c272dd1c4" + }, + { + "vendorName": "Eshgro Smarter365", + "vendorTenantId": "0a3132f8-cbcd-430f-a554-b0490bea8018" + }, + { + "vendorName": "BitTitan", + "vendorTenantId": "6690473e-14f0-4f77-bf88-2ae5ade8746c" + }, + { + "vendorName": "Ingram Micro CPV", + "vendorTenantId": "5abe7315-18a5-4ca1-8e51-99753bef0d96" + }, + { + "vendorName": "CodeTwo", + "vendorTenantId": "d4fd5c8b-42b0-4e2d-b330-498809917d07" + }, + { + "vendorName": "Anywhere365", + "vendorTenantId": "4179dd4d-e485-4535-9d45-b3539f584cad" + }, + { + "vendorName": "Roger365", + "vendorTenantId": "3c59ec71-67c8-4f3d-b2bd-19152b2629e1" + }, + { + "vendorName": "ESET", + "vendorTenantId": "01f7e0e8-c680-4293-8068-d572231a88f4" + }, + { + "vendorName": "CyberTwice", + "vendorTenantId": "0c1fb16c-0190-47e0-b839-5ec8665eb699" + }, + { + "vendorName": "CloudMore AB", + "vendorTenantId": "0cc4f6a9-d96a-4508-b938-32386e1c44cf" + }, + { + "vendorName": "Google Workspace", + "vendorTenantId": "0f8cb250-b44f-4acd-b24e-2524ef9f85ac" + }, + { + "vendorName": "Merill", + "vendorTenantId": "10407d69-1ba5-4bec-8ebe-9af2f0b9e06a" + }, + { + "vendorName": "Citrix Cloud", + "vendorTenantId": "13d925d3-c1fe-4447-9600-bb8572753a33" + }, + { + "vendorName": "Proofpoint", + "vendorTenantId": "1d308ae7-d771-4a4a-8857-409aee86644a" + }, + { + "vendorName": "Datto RMM", + "vendorTenantId": "353488c3-d84b-48e4-95ba-2456645d67bf" + }, + { + "vendorName": "Avepoint", + "vendorTenantId": "3ee8362e-4c3c-447b-9a9e-910eec5d89c8" + }, + { + "vendorName": "SkyKick", + "vendorTenantId": "3f9cd7a0-127a-4ed4-bf06-2f830d5616b7" + }, + { + "vendorName": "Carbonite", + "vendorTenantId": "4f0566c5-7c80-40a4-b3e8-7cfcb6c423ff" + }, + { + "vendorName": "Hornet Security/Altaro", + "vendorTenantId": "58fba382-61c2-47f8-92b1-296e14787b85" + }, + { + "vendorName": "N-able", + "vendorTenantId": "6324f4fb-86ee-4493-ba16-c819a916b487" + }, + { + "vendorName": "Freshdesk", + "vendorTenantId": "6cdfee3a-1922-41c3-8e74-f4ae694fae8a" + }, + { + "vendorName": "3CX", + "vendorTenantId": "6e2eadce-56c5-4116-a790-14d391b48eac" + }, + { + "vendorName": "Pckgr", + "vendorTenantId": "6fd3fafe-81c9-41da-b84b-143ba2908cbb" + }, + { + "vendorName": "AdminDroid", + "vendorTenantId": "7228f07f-efee-4159-9393-be6efb253fd0" + }, + { + "vendorName": "Exclaimer", + "vendorTenantId": "74bebcc8-1c15-4717-b538-bf84dea9e50f" + }, + { + "vendorName": "Bastion365", + "vendorTenantId": "7bfd6512-414b-4fb1-9529-3ac3f9e7ec26" + }, + { + "vendorName": "Ricoh", + "vendorTenantId": "7e7a67d3-42f4-4a40-b8f0-f998ca7018ab" + }, + { + "vendorName": "Hubspot", + "vendorTenantId": "8c6c4f2c-3b97-427e-ab0f-1b982441831a" + }, + { + "vendorName": "TrendMicro", + "vendorTenantId": "8d3dd3ec-a7ee-4c65-8cea-01e3f1492f1b" + }, + { + "vendorName": "Synology", + "vendorTenantId": "9ba572a0-0623-4ab6-96ad-74cf9f3631fe" + }, + { + "vendorName": "Scappman", + "vendorTenantId": "b2de4365-2ff4-4021-ac3b-fd1d2abb8de7" + }, + { + "vendorName": "Barracuda Networks", + "vendorTenantId": "b893e0b8-2743-4fa7-81eb-0155a9060350" + }, + { + "vendorName": "Zivver", + "vendorTenantId": "c3ea126d-08dd-4b6a-a23a-4a513160c11e" + }, + { + "vendorName": "HP", + "vendorTenantId": "ca7981a2-785a-463d-b82a-3db87dfc3ce6" + }, + { + "vendorName": "ManageEngine", + "vendorTenantId": "dab88ba4-c480-4a0f-a42b-6d66fba460ff" + }, + { + "vendorName": "Action1", + "vendorTenantId": "e1a62259-76ab-47a0-9e9d-b91f3b08cbd5" + }, + { + "vendorName": "ShareGate", + "vendorTenantId": "eb39acb7-fae3-4bc3-974c-b765aa1d6355" + }, + { + "vendorName": "Liquit", + "vendorTenantId": "2ea2a5a1-1e01-4ecc-ba61-12fb51c1c12d" + }, + { + "vendorName": "Phished", + "vendorTenantId": "9f7b0cc1-2df8-442e-b902-84371e12af00" + }, + { + "vendorName": "Octiga Security", + "vendorTenantId": "b7453564-2f58-4ad6-b7ba-c6e35e8de6bb" + }, + { + "vendorName": "Printix", + "vendorTenantId": "2e5d89d1-0b98-45d8-b70f-c79db10feb54" + }, + { + "vendorName": "Usecure", + "vendorTenantId": "7d88f9a5-1574-4b92-82e4-e05712338cf4" + }, + { + "vendorName": "Redstor", + "vendorTenantId": "24ac53ae-15a7-4211-afef-61d8f34e2571" + }, + { + "vendorName": "Axcient", + "vendorTenantId": "adc01291-a3ea-4fb8-8fa7-d8f6dd816182" + }, + { + "vendorName": "NinjaOne", + "vendorTenantId": "0e0adb39-f83f-4576-9102-db1b902ca108" + }, + { + "vendorName": "SuperVision (KPN)", + "vendorTenantId": "8edc1ef5-a81d-4229-badb-e2634a284461" + }, + { + "vendorName": "MSPMagic", + "vendorTenantId": "74d3d0de-bbd7-433f-95c2-40cc5d185968" + }, + { + "vendorName": "SimeonCloud", + "vendorTenantId": "3d945cb7-f7da-444c-8c9e-93c3226581ec" + }, + { + "vendorName": "KeepIt", + "vendorTenantId": "55c67891-c2e6-4278-a07e-5391e269777e" + }, + { + "vendorName": "Arrow Sphere", + "vendorTenantId": "0beb0c35-9cbb-4feb-99e5-589e415c7944" + }, + { + "vendorName": "Auvik", + "vendorTenantId": "408f0e54-582e-46fa-8b7d-4068bb176500" + }, + { + "vendorName": "Avanan", + "vendorTenantId": "5c37d5b5-54f7-46e7-bc09-2eaa1ef1b541" + }, + { + "vendorName": "CloudRadial", + "vendorTenantId": "845fbb26-e9d0-465d-8d4f-40f490928179" + }, + { + "vendorName": "Duo", + "vendorTenantId": "3c453cf7-43fe-4e45-a9b9-f168f480f0f8" + }, + { + "vendorName": "Metallic", + "vendorTenantId": "da72dd62-58c6-4062-abf9-47be4e73c0f6" + }, + { + "vendorName": "Sophos", + "vendorTenantId": "358a41ff-46d9-49d3-a297-370d894eae6a" + }, + { + "vendorName": "5c7b2b48-9e8f-49ba-80d6-3432e39d596b", + "vendorTenantId": "SaaSAlerts" + } +] \ No newline at end of file diff --git a/src/views/tenant/administration/TenantOffboardingWizard.jsx b/src/views/tenant/administration/TenantOffboardingWizard.jsx index d5cd00a4fb60..38baf73903d8 100644 --- a/src/views/tenant/administration/TenantOffboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOffboardingWizard.jsx @@ -5,10 +5,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons' import { useSelector } from 'react-redux' import { CippWizard } from 'src/components/layout' +import vendors1 from 'src/data/vendorTenantList' import PropTypes from 'prop-types' import { RFFCFormCheck, RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms' import { TenantSelector } from 'src/components/utilities' -import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' const Error = ({ name }) => ( { const currentSettings = useSelector((state) => state.app) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const filterParts = vendors1.map((vendor) => `appOwnerOrganizationId eq ${vendor.vendorTenantId}`) + const filter = filterParts.join(' or ') + + const { + data: vendorApps = [], + vendorAppsIsFetching, + vendorAppsIsSuccess, + } = useGenericGetRequestQuery({ + path: 'api/ListGraphRequest', + params: { + TenantFilter: tenantDomain, + Endpoint: 'servicePrincipals', + $filter: filter, + $select: 'appId,displayName,appOwnerOrganizationId', + $count: true, + }, + }) + const handleSubmit = async (values) => { const shippedValues = { TenantFilter: tenantDomain, + RemoveVendorApps: values.vendorApplications ? values.vendorApplications : '', RemoveCSPGuestUsers: values.RemoveCSPGuestUsers ? values.RemoveCSPGuestUsers : '', RemoveCSPnotificationContacts: values.RemoveCSPnotificationContacts ? values.RemoveCSPnotificationContacts @@ -80,6 +100,20 @@ const TenantOffboardingWizard = () => {
+ {vendorAppsIsFetching && } + {!vendorAppsIsFetching && ( + ({ + value: vendor.appId, + name: vendor.displayName, + }))} + /> + )} +
{ name="RemoveCSPnotificationContacts" label="Remove all notification contacts originating from the CSP tenant (technical,security,marketing notifications)." /> +
+
These actions will terminate all delegated access to the customer tenant! @@ -156,6 +192,14 @@ const TenantOffboardingWizard = () => { + + Remove vendor applications + + Remove all notification contacts originating from the CSP tenant (technical,security,marketing notifications) From ed2116730eae96f6f58b3fe4416f7009c6462198 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Wed, 20 Dec 2023 08:28:00 +0100 Subject: [PATCH 17/51] Update vendorTenantList.json --- src/data/vendorTenantList.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/vendorTenantList.json b/src/data/vendorTenantList.json index f6fac9ed08d4..5bdebccccde3 100644 --- a/src/data/vendorTenantList.json +++ b/src/data/vendorTenantList.json @@ -232,7 +232,7 @@ "vendorTenantId": "358a41ff-46d9-49d3-a297-370d894eae6a" }, { - "vendorName": "5c7b2b48-9e8f-49ba-80d6-3432e39d596b", - "vendorTenantId": "SaaSAlerts" + "vendorName": "SaaSAlerts", + "vendorTenantId": "5c7b2b48-9e8f-49ba-80d6-3432e39d596b" } -] \ No newline at end of file +] From a74596f7f6d0f84eeb4bfe86ded8214335554184 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 20 Dec 2023 10:36:30 -0500 Subject: [PATCH 18/51] Frontend tweaks Add callouts for GDAP check warn/error Add start onboarding action from relationship page offcanvas --- src/views/cipp/CIPPSettings.jsx | 55 +++++++++++++------ .../administration/ListGDAPRelationships.jsx | 7 +++ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/views/cipp/CIPPSettings.jsx b/src/views/cipp/CIPPSettings.jsx index f61fa935969b..d8c19473d03d 100644 --- a/src/views/cipp/CIPPSettings.jsx +++ b/src/views/cipp/CIPPSettings.jsx @@ -485,24 +485,43 @@ const GeneralSettings = () => { {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length > 0 && ( - + <> + {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Error') + .length > 0 && ( + + Relationship errors detected. Review the table below for more details. + + )} + {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Warning') + .length > 0 && ( + + Relationship warnings detected. Review the table below for more details. + + )} + e.Type === 'Error') + .length > 0 + ? 'Complex: Type eq Error' + : 'Complex: Type eq Warning' + } + isModal={true} + /> + )} {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length === 0 && ( diff --git a/src/views/tenant/administration/ListGDAPRelationships.jsx b/src/views/tenant/administration/ListGDAPRelationships.jsx index ca1caccd237c..7987176646db 100644 --- a/src/views/tenant/administration/ListGDAPRelationships.jsx +++ b/src/views/tenant/administration/ListGDAPRelationships.jsx @@ -87,6 +87,13 @@ const Actions = (row, rowIndex, formatExtraData) => { title={'GDAP - ' + row?.customer?.displayName} extendedInfo={extendedInfo} actions={[ + { + label: 'Start Onboarding', + color: 'primary', + link: + '/tenant/administration/tenant-onboarding-wizard?tableFilter=Complex: id eq ' + + row.id, + }, { label: 'Enable automatic extension', color: 'info', From c0449040203642d66e83b8b75a7f25bf868d400f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 20 Dec 2023 11:09:03 -0500 Subject: [PATCH 19/51] Add domain expand --- src/views/home/Home.jsx | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/views/home/Home.jsx b/src/views/home/Home.jsx index 0bf92e0bc991..e667c9fc0cbd 100644 --- a/src/views/home/Home.jsx +++ b/src/views/home/Home.jsx @@ -33,7 +33,7 @@ import { TableModalButton } from 'src/components/buttons' const Home = () => { const [visible, setVisible] = useState(false) - + const [domainVisible, setDomainVisible] = useState(false) const currentTenant = useSelector((state) => state.app.currentTenant) const { data: organization, @@ -292,11 +292,31 @@ const Home = () => {

Domain(s)

{(isLoadingOrg || isFetchingOrg) && } - {!isFetchingOrg && - issuccessOrg && - organization?.verifiedDomains?.map((item, idx) => ( -
  • {item.name}
  • - ))} + <> + {!isFetchingOrg && issuccessOrg && ( + <> + {organization.verifiedDomains?.slice(0, 5).map((item, idx) => ( +
  • {item.name}
  • + ))} + {organization.verifiedDomains?.length > 5 && ( + <> + + {organization.verifiedDomains?.slice(5).map((item, idx) => ( +
  • {item.name}
  • + ))} +
    + setDomainVisible(!domainVisible)} + > + {domainVisible ? 'See less' : 'See more...'} + + + )} + + )} +

    Capabilities

    From be9045cc6031f421ede215fefc283de5ff60acb2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 20 Dec 2023 17:28:00 -0500 Subject: [PATCH 20/51] GDAP Invite wizard Change to table output with CSV export Add progress bar for tracking invite creation progress --- .../administration/GDAPInviteWizard.jsx | 85 ++++++++++++------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index e3c2ce2a1d81..e4d5c0b9c9ed 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -8,16 +8,17 @@ import { CFormInput, CFormLabel, CFormRange, + CProgress, } from '@coreui/react' import { Field, FormSpy } from 'react-final-form' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' import { CippWizard } from 'src/components/layout' -import { WizardTableField } from 'src/components/tables' +import { CippTable, WizardTableField } from 'src/components/tables' import { TitleButton } from 'src/components/buttons' import PropTypes from 'prop-types' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' -import { CippCodeBlock } from 'src/components/utilities' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' const Error = ({ name }) => ( { setLoopRunning(true) for (var x = 0; x < inviteCount; x++) { const results = await genericPostRequest({ path: '/api/ExecGDAPInvite', values: values }) - resultsarr.push(results) + resultsarr.push(results.data) setMassResults(resultsarr) } setLoopRunning(false) @@ -60,6 +61,30 @@ const GDAPInviteWizard = () => { const formValues = {} + const inviteColumns = [ + { + name: 'Id', + selector: (row) => row?.Invite?.RowKey, + exportSelector: 'Invite/RowKey', + sortable: true, + cell: cellGenericFormatter(), + }, + { + name: 'Invite Link', + sortable: true, + selector: (row) => row?.Invite?.InviteUrl, + exportSelector: 'Invite/InviteUrl', + cell: cellGenericFormatter(), + }, + { + name: 'Onboarding Link', + sortable: true, + selector: (row) => row?.Invite?.OnboardingUrl, + exportSelector: 'Invite/OnboardingUrl', + cell: cellGenericFormatter(), + }, + ] + return ( { }} )} - {(massResults.length >= 1 || loopRunning) && - massResults.map((message, idx) => { - const results = message?.data - const displayResults = Array.isArray(results) ? results.join(', ') : results - return ( - - {results.Results.map((message, idx) => { - return
  • {message}
  • - })} - + {(massResults.length >= 1 || loopRunning) && ( + <> +
    + {loopRunning ? ( + + Generating Invites + + ) : ( + + Generating Invites + + + )} + + {massResults.length}/{inviteCount} + +
    - -
    - ) - })} - {loopRunning && ( -
  • - -
  • + + )}
    From cd792782e3bd15c94f293af079b6730204695954 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 20 Dec 2023 17:37:47 -0500 Subject: [PATCH 21/51] Update GDAPInviteWizard.jsx --- src/views/tenant/administration/GDAPInviteWizard.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index e4d5c0b9c9ed..d1c1a55be472 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -83,6 +83,13 @@ const GDAPInviteWizard = () => { exportSelector: 'Invite/OnboardingUrl', cell: cellGenericFormatter(), }, + { + name: 'Message', + sortable: true, + selector: (row) => row?.Message, + exportSelector: 'Message', + cell: cellGenericFormatter(), + }, ] return ( From 3a048c25ce70c3202e3b9a6e424077ece5fdb3ea Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 20 Dec 2023 17:43:58 -0500 Subject: [PATCH 22/51] Hide id by default --- src/views/tenant/administration/GDAPInviteWizard.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index d1c1a55be472..2377ae2e0239 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -67,6 +67,7 @@ const GDAPInviteWizard = () => { selector: (row) => row?.Invite?.RowKey, exportSelector: 'Invite/RowKey', sortable: true, + omit: true, cell: cellGenericFormatter(), }, { From 0223399d7507505741a85c7fea270b2db0493a12 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 22 Dec 2023 01:15:21 +0100 Subject: [PATCH 23/51] new alerts --- src/_nav.jsx | 8 +- src/components/tables/CippOffcanvasTable.jsx | 2 +- .../utilities/CippActionsOffcanvas.jsx | 2 +- .../utilities/CippCodeOffcanvas.jsx | 2 +- src/routes.js | 4 + src/views/cipp/Scheduler.jsx | 16 +- .../tenant/administration/AlertRules.jsx | 572 ++++++++++++++++++ .../tenant/administration/AlertWizard.jsx | 110 +--- .../tenant/administration/ListAlertsQueue.jsx | 525 ++++++++-------- 9 files changed, 859 insertions(+), 382 deletions(-) create mode 100644 src/views/tenant/administration/AlertRules.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index 1202c3838a9a..77ed1af3a328 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -124,13 +124,13 @@ const _nav = [ }, { component: CNavItem, - name: 'Alerts Wizard', - to: '/tenant/administration/alertswizard', + name: 'Alerts (Classic)', + to: '/tenant/administration/alertsqueue', }, { component: CNavItem, - name: 'Alerts Configuration', - to: '/tenant/administration/alertsqueue', + name: 'Alert Rules', + to: '/tenant/administration/AlertRules', }, { component: CNavItem, diff --git a/src/components/tables/CippOffcanvasTable.jsx b/src/components/tables/CippOffcanvasTable.jsx index fe2f7e0e1c7c..b990c28366cb 100644 --- a/src/components/tables/CippOffcanvasTable.jsx +++ b/src/components/tables/CippOffcanvasTable.jsx @@ -10,7 +10,7 @@ export default function CippOffcanvasTable({ rows }) { )) return ( - + {tableRows} ) diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index fb378db83f8e..20e84a76b0bc 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -301,7 +301,7 @@ export default function CippActionsOffcanvas(props) { Extended Information - {extendedInfoContent} + {extendedInfoContent} {cardContent && cardContent} {Actions} diff --git a/src/components/utilities/CippCodeOffcanvas.jsx b/src/components/utilities/CippCodeOffcanvas.jsx index ce09d50e7db1..589c27ce3cb8 100644 --- a/src/components/utilities/CippCodeOffcanvas.jsx +++ b/src/components/utilities/CippCodeOffcanvas.jsx @@ -36,7 +36,7 @@ function CippCodeOffCanvas({ addedClass="offcanvas-large" placement="end" visible={visible} - id={row.id} + id={row} hideFunction={hideFunction} > import('src/views/pages/page500/Page500')) const MFAReport = React.lazy(() => import('src/views/identity/reports/MFAReport')) const Tenants = React.lazy(() => import('src/views/tenant/administration/Tenants')) const AlertWizard = React.lazy(() => import('src/views/tenant/administration/AlertWizard')) +const AlertRules = React.lazy(() => import('src/views/tenant/administration/AlertRules')) + const AlertsQueue = React.lazy(() => import('src/views/tenant/administration/ListAlertsQueue')) const GraphExplorer = React.lazy(() => import('src/views/tenant/administration/GraphExplorer')) @@ -311,6 +313,8 @@ const routes = [ { path: '/tenant/administration/tenants/edit', name: 'Edit Tenant', component: EditTenant }, { path: '/tenant/administration/domains', name: 'Domains', component: Domains }, { path: '/tenant/administration/alertswizard', name: 'Alerts Wizard', component: AlertWizard }, + { path: '/tenant/administration/alertrules', name: 'Alerts Wizard', component: AlertRules }, + { path: '/tenant/administration/alertsqueue', name: 'Alerts Queue', component: AlertsQueue }, { path: '/tenant/administration/graph-explorer', diff --git a/src/views/cipp/Scheduler.jsx b/src/views/cipp/Scheduler.jsx index 35da84f34e04..77c07163feb2 100644 --- a/src/views/cipp/Scheduler.jsx +++ b/src/views/cipp/Scheduler.jsx @@ -1,20 +1,14 @@ import React, { useEffect, useState } from 'react' import { CButton, CCallout, CCol, CForm, CFormLabel, CRow, CSpinner, CTooltip } from '@coreui/react' import useQuery from 'src/hooks/useQuery' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { Field, Form, FormSpy } from 'react-final-form' import { - Condition, - RFFCFormCheck, RFFCFormInput, RFFCFormInputArray, - RFFCFormRadio, RFFCFormSwitch, - RFFCFormTextarea, RFFSelectSearch, } from 'src/components/forms' -import countryList from 'src/data/countryList' - import { useGenericGetRequestQuery, useLazyGenericGetRequestQuery, @@ -24,13 +18,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' import { CippContentCard, CippPage, CippPageList } from 'src/components/layout' import { password } from 'src/validators' -import { - CellDate, - CellDelegatedPrivilege, - cellBadgeFormatter, - cellBooleanFormatter, - cellDateFormatter, -} from 'src/components/tables' +import { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables' import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx new file mode 100644 index 000000000000..1dd7d88b7f7d --- /dev/null +++ b/src/views/tenant/administration/AlertRules.jsx @@ -0,0 +1,572 @@ +import React, { useEffect, useState } from 'react' +import { CButton, CCallout, CCol, CForm, CFormLabel, CRow, CSpinner, CTooltip } from '@coreui/react' +import useQuery from 'src/hooks/useQuery' +import { useSelector } from 'react-redux' +import { Field, Form, FormSpy } from 'react-final-form' +import { + Condition, + RFFCFormInput, + RFFCFormInputArray, + RFFCFormSelect, + RFFCFormSwitch, + RFFSelectSearch, +} from 'src/components/forms' +import { + useGenericGetRequestQuery, + useLazyGenericGetRequestQuery, + useLazyGenericPostRequestQuery, +} from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' +import { CippContentCard, CippPage, CippPageList } from 'src/components/layout' +import { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables' +import { CellTip } from 'src/components/tables/CellGenericFormat' +import 'react-datepicker/dist/react-datepicker.css' +import TenantListSelector from 'src/components/utilities/TenantListSelector' +import { ModalService, TenantSelector } from 'src/components/utilities' +import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' +import arrayMutators from 'final-form-arrays' + +const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const [ocVisible, setOCVisible] = useState(false) + + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => ExecuteGetRequest({ path: apiurl }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row.Results) + } catch (error) { + jsonResults = row.Results + } + + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveScheduledItem?&ID=${row.RowKey}`, + 'Do you want to delete this job?', + ) + } + size="sm" + variant="ghost" + color="danger" + > + + + + setOCVisible(false)} + /> + + ) +} + +const AlertRules = () => { + const currentDate = new Date() + const [startDate, setStartDate] = useState(currentDate) + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [isArr, setisArr] = useState([]) + const [refreshState, setRefreshState] = useState(false) + const taskName = `Scheduled Task ${currentDate.toLocaleString()}` + const { data: availableCommands = [], isLoading: isLoadingcmd } = useGenericGetRequestQuery({ + path: 'api/ListFunctionParameters?Module=CIPPCore', + }) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + const unixTime = Math.floor(startDate.getTime() / 1000) + const shippedValues = { + TenantFilter: tenantDomain, + Name: values.taskName, + Command: values.command, + Parameters: values.parameters, + ScheduledTime: unixTime, + Recurrence: values.Recurrence, + AdditionalProperties: values.additional, + PostExecution: { + Webhook: values.webhook, + Email: values.email, + PSA: values.psa, + }, + } + genericPostRequest({ path: '/api/AddScheduledItem', values: shippedValues }).then((res) => { + setRefreshState(res.requestId) + }) + } + const columns = [ + { + name: 'Name', + selector: (row) => row['Name'], + sortable: true, + cell: (row) => CellTip(row['Name']), + exportSelector: 'Name', + }, + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: (row) => CellTip(row['Tenant']), + exportSelector: 'Tenant', + }, + { + name: 'Task State', + selector: (row) => row['TaskState'], + sortable: true, + cell: cellBadgeFormatter(), + exportSelector: 'TaskState', + }, + { + name: 'Command', + selector: (row) => row['Command'], + sortable: true, + cell: (row) => CellTip(row['Command']), + exportSelector: 'Command', + }, + { + name: 'Parameters', + selector: (row) => row['Parameters'], + sortable: true, + cell: (row) => CellTip(row['Parameters']), + exportSelector: 'Parameters', + }, + { + name: 'Scheduled Time', + selector: (row) => row['ScheduledTime'], + sortable: true, + cell: cellDateFormatter({ format: 'relative' }), + exportSelector: 'ScheduledTime', + }, + { + name: 'Last executed time', + selector: (row) => row['ExecutedTime'], + sortable: true, + cell: cellDateFormatter({ format: 'relative' }), + exportSelector: 'ExecutedTime', + }, + { + name: 'Recurrence', + selector: (row) => row['Recurrence'], + sortable: true, + cell: (row) => CellTip(row['Recurrence']), + exportSelector: 'Recurrence', + }, + { + name: 'Sending to', + selector: (row) => row['PostExecution'], + sortable: true, + cell: (row) => CellTip(row['PostExecution']), + exportSelector: 'PostExecution', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, + ] + + const ifvalues = [ + { value: 'New-InboxRule', label: 'A new Inbox rule is created' }, + { value: 'Set-InboxRule', label: 'A existing Inbox rule is created' }, + { + value: 'Add member to role.', + label: 'A user has been added to an admin role', + }, + { + value: 'Disable account.', + label: 'A user account has been disabled', + }, + { + value: 'Enable account.', + label: 'A user account has been enabled', + }, + { + value: 'Update StsRefreshTokenValidFrom Timestamp.', + label: 'A user sessions have been revoked', + }, + { + value: 'Disable Strong Authentication.', + label: 'A users MFA has been disabled', + }, + { + value: 'Remove Member from a role.', + label: 'A user has been removed from a role', + }, + { + value: 'Reset user password.', + label: 'A user password has been reset', + }, + { + value: 'UserLoggedInFromUnknownLocation', + label: 'A user has logged in from a location ', + }, + { + value: 'Add service principal', + label: 'A service prinicipal has been created', + }, + { + value: 'Remove service principal.', + label: 'A service principal has been removed', + }, + { + value: 'ImpossibleTravel', + label: 'A user has logged in from an impossible location (Based on IP)', + }, + { + value: 'badRepIP', + label: 'A user has logged in from a known bad-reputation IP', + }, + { value: 'customField', label: 'Custom Log Query' }, + ] + const dovalues = [ + { value: 'cippcommand', label: 'Execute a CIPP Command' }, + { value: 'becremediate', label: 'Execute a BEC Remediate' }, + { value: 'disableuser', label: 'Disable the user in the log entry' }, + { value: 'generatelog', label: 'Generate a log entry' }, + { value: 'generatemail', label: 'Generate an email' }, + { value: 'generatePSA', label: 'Generate a PSA ticket' }, + { value: 'generateWebhook', label: 'Forward the log as webhook' }, + { + value: 'store', + label: 'Store the log into an external Azure Storage Account', + }, + ] + + const [ifCount, setIfCount] = useState(1) + const [doCount, setDoCount] = useState(1) + + const handleButtonIf = (operator) => { + if (operator === '+') { + if (ifCount < 3) { + setIfCount(ifCount + 1) + } + } else { + if (ifCount > 1) { + setIfCount(ifCount - 1) + } + } + } + + const handleButtonDo = (operator) => { + if (operator === '+') { + if (doCount < 10) { + setDoCount(doCount + 1) + } + } else { + if (doCount > 1) { + setDoCount(doCount - 1) + } + } + } + + const renderIfs = () => { + const ifs = [] + for (let i = 0; i < ifCount; i++) { + ifs.push( + <> + {i === 0 ? 'If' : 'And'} + + + + + + {ifCount > 1 && ( + handleButtonIf('-')} + disabled={doCount >= 10} + > + + + )} + + + handleButtonIf('+')} + disabled={ifCount >= 3} + > + + + + + + + + + + + + , + ) + } + return ifs + } + + const renderDos = () => { + const dos = [] + + for (let i = 0; i < doCount; i++) { + dos.push( + <> + {i === 0 ? 'Execute this' : 'And'} + + + + + {doCount > 1 && ( + + handleButtonDo('-')} + disabled={doCount >= 10} + > + + + + )} + + handleButtonDo('+')} + disabled={doCount >= 10} + > + + + + + + + ({ + value: cmd.Function, + name: cmd.Function, + }))} + name={`command`} + placeholder={ + isLoadingcmd ? ( + + ) : ( + 'Select a command or report to execute.' + ) + } + label="Command to execute" + /> + + + + + + + + + + + + , + ) + } + return dos + } + + return ( + + <> + + + +
    true} + render={({ handleSubmit, submitting, values }) => { + return ( + + + + + {(props) => } + + + {renderIfs()} + {renderDos()} + + + {/* eslint-disable react/prop-types */} + {(props) => { + const selectedCommand = availableCommands.find( + (cmd) => cmd.Function === props.values.command?.value, + ) + return ( + + {selectedCommand?.Synopsis} + + ) + }} + + + + {/* eslint-disable react/prop-types */} + {(props) => { + const selectedCommand = availableCommands.find( + (cmd) => cmd.Function === props.values.command?.value, + ) + let paramblock = null + if (selectedCommand) { + //if the command parameter type is boolean we use else . + const parameters = selectedCommand.Parameters + if (parameters.length > 0) { + paramblock = parameters.map((param, idx) => ( + + + + {param.Type === 'System.Boolean' || + param.Type === + 'System.Management.Automation.SwitchParameter' ? ( + <> + + + + ) : ( + <> + {param.Type === 'System.Collections.Hashtable' ? ( + + ) : ( + + )} + + )} + + + + )) + } + } + return paramblock + }} + + + + + + Add Schedule + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + +
  • {postResults.data.Results}
  • +
    + )} +
    + ) + }} + /> + + + + + + + + + ) +} + +export default AlertRules diff --git a/src/views/tenant/administration/AlertWizard.jsx b/src/views/tenant/administration/AlertWizard.jsx index 40bf89a36870..c2e920354e15 100644 --- a/src/views/tenant/administration/AlertWizard.jsx +++ b/src/views/tenant/administration/AlertWizard.jsx @@ -91,13 +91,13 @@ const AlertWizard = () => {

    Step 2

    -
    Select alerts to receive
    +
    Select Legacy Alerts to receive

    - Alerts setup on this page will be sent to webhook configured in CIPPs settings, and be - delivered as messages + Alerts setup on this page are considered legacy. These alerts run every 15 minutes and + do not use our advanced alerting engine.

    { /> +
    { name="NoCAConfig" label="Alert on tenants without a Conditional Access policy, while having Conditional Access licensing available." /> - + + + + + + + { name="DefenderMalware" label="Alert on Defender Malware found (Tenant must be on-boarded in Lighthouse)" /> - - - - - - { - -
    @@ -168,78 +167,15 @@ const AlertWizard = () => {

    - These alerts are received directly from the audit log, and will be processed as soon as - Microsoft sends them to CIPP. These alerts generate a ticket, email or webhook message - per alert, with more information about the alert. -

    - -

    - "Alerts setup on this page will be sent to the webhook configured in CIPPs settings, and - be delivered as raw json information. Warning: Teams, Slack, and Discord do not support - receiving raw json messages" + This setting will subscribe CIPP to receive the audit logs from this tenant directly. + You can then use the Alert Rules page to create alerts or take actions based on these + logs.

    - - - - - - - ({ - value: Code, - name: Name, - }))} + diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index b2c21f394f67..939a08a76d66 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -1,301 +1,278 @@ -import React from 'react' +import React, { useState } from 'react' +import { CButton, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' import { useSelector } from 'react-redux' -import { CSpinner, CButton, CCallout, CRow } from '@coreui/react' -import { faTrash } from '@fortawesome/free-solid-svg-icons' +import { Field, Form } from 'react-final-form' +import { RFFCFormSwitch } from 'src/components/forms' +import { + useGenericGetRequestQuery, + useLazyGenericGetRequestQuery, + useLazyGenericPostRequestQuery, +} from 'src/store/api/app' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { CippPageList } from 'src/components/layout' -import { ModalService } from 'src/components/utilities' -import { useLazyGenericGetRequestQuery } from 'src/store/api/app' -import { cellBooleanFormatter } from 'src/components/tables' +import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' +import { CippContentCard, CippPage, CippPageList } from 'src/components/layout' import { CellTip } from 'src/components/tables/CellGenericFormat' +import 'react-datepicker/dist/react-datepicker.css' +import { CippActionsOffcanvas, ModalService, TenantSelector } from 'src/components/utilities' +import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' +import arrayMutators from 'final-form-arrays' -const ListAlertsQueue = () => { +const Offcanvas = (row, rowIndex, formatExtraData) => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() - const Actions = (row, index, column) => { - const handleDeleteStandard = (apiurl, message) => { - ModalService.confirm({ - title: 'Confirm', - body:
    {message}
    , - onConfirm: () => ExecuteGetRequest({ path: apiurl }), - confirmLabel: 'Continue', - cancelLabel: 'Cancel', - }) - } - return ( - - handleDeleteStandard( - `api/RemoveQueuedAlert?ID=${row.tenantId}`, - 'Do you want to delete the queued alert?', - ) - } - > - - - ) + const [ocVisible, setOCVisible] = useState(false) + + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => ExecuteGetRequest({ path: apiurl }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row) + } catch (error) { + jsonResults = row } - const ActionsWebhook = (row, index, column) => { - const handleDeleteStandard = (apiurl, message) => { - ModalService.confirm({ - title: 'Confirm', - body:
    {message}
    , - onConfirm: () => ExecuteGetRequest({ path: apiurl }), - confirmLabel: 'Continue', - cancelLabel: 'Cancel', - }) - } - return ( - - handleDeleteStandard( - `api/RemoveWebhookAlert?CIPPID=${row.RowKey}&TenantFilter=${row.PartitionKey}`, - 'Do you want to delete the queued alert?', - ) - } - > - - - ) + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveQueuedAlert?&ID=${row.tenantId}`, + 'Do you want to delete the queued alert?', + ) + } + size="sm" + variant="ghost" + color="danger" + > + + + + ({ + label: key, + value: + typeof row[key] === 'boolean' ? ( + row[key] ? ( + + ) : ( + + ) + ) : ( + row[key] + ), + }))} + placement="end" + visible={ocVisible} + id={row.id} + hideFunction={() => setOCVisible(false)} + /> + + ) +} + +const ListClassicAlerts = () => { + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [refreshState, setRefreshState] = useState(false) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + Object.keys(values).filter(function (x) { + if (values[x] === null) { + delete values[x] + } + return null + }) + values['tenantFilter'] = tenantDomain + values['SetAlerts'] = true + genericPostRequest({ path: '/api/AddAlert', values: values }) } - const webhookcolumns = [ - { - name: 'Tenant', - selector: (row) => row['PartitionKey'], - sortable: true, - exportSelector: 'PartitionKey', - }, - { - name: 'Expiration', - selector: (row) => row['Expiration'], - sortable: true, - exportSelector: 'Expiration', - cell: (row) => CellTip(row['Expiration']), - }, - { - name: 'Monitored Resource', - selector: (row) => row['Resource'], - sortable: true, - exportSelector: 'Resource', - cell: (row) => CellTip(row['Resource']), - }, - { - name: 'Monitored Actions', - selector: (row) => row['Operations'], - sortable: true, - exportSelector: 'Operations', - cell: (row) => CellTip(row['Operations']), - }, - { - name: 'Webhook URL', - selector: (row) => row['WebhookNotificationUrl'], - sortable: true, - exportSelector: 'WebhookNotificationUrl', - cell: (row) => CellTip(row['WebhookNotificationUrl']), - }, - { - name: 'Actions', - cell: ActionsWebhook, - }, - ] + const { data: currentlySelectedAlerts = [], isLoading: isLoadingCurrentAlerts } = + useGenericGetRequestQuery({ + path: `api/ListAlertsQueue?TenantFilter=${tenantDomain}&RefreshGuid=${refreshState}`, + }) const columns = [ { - name: 'Tenant', + name: 'Tenant Name', selector: (row) => row['tenantName'], sortable: true, + cell: (row) => CellTip(row['tenantName']), exportSelector: 'tenantName', }, { - name: 'Changed Admin Passwords', - selector: (row) => row['AdminPassword'], - sortable: true, - exportSelector: 'AdminPassword', - cell: cellBooleanFormatter(), - }, - { - name: 'Defender Malware Alerts', - selector: (row) => row['DefenderMalware'], - sortable: true, - exportSelector: 'DefenderMalware', - cell: cellBooleanFormatter(), - }, - { - name: 'MFA for Admins', - selector: (row) => row['MFAAdmins'], - sortable: true, - exportSelector: 'MFAAdmins', - cell: cellBooleanFormatter(), - }, - { - name: 'MFA for Users', - selector: (row) => row['MFAAlertUsers'], - sortable: true, - exportSelector: 'MFAAlertUsers', - cell: cellBooleanFormatter(), - }, - { - name: 'Changes to Admin Roles', - selector: (row) => row['NewRole'], + name: 'Tenant ID', + selector: (row) => row['tenantId'], sortable: true, - exportSelector: 'NewRole', - cell: cellBooleanFormatter(), - }, - { - name: 'Exchange Mailbox size', - selector: (row) => row['QuotaUsed'], - sortable: true, - exportSelector: 'QuotaUsed', - cell: cellBooleanFormatter(), - }, - { - name: 'Unused Licenses', - selector: (row) => row['UnusedLicenses'], - sortable: true, - exportSelector: 'UnusedLicenses', - cell: cellBooleanFormatter(), - }, - { - name: 'Overused Licenses', - selector: (row) => row['OverusedLicenses'], - sortable: true, - exportSelector: 'OverusedLicenses', - cell: cellBooleanFormatter(), - }, - { - name: 'App Secret Expiry', - selector: (row) => row['AppSecretExpiry'], - sortable: true, - exportSelector: 'AppSecretExpiry', - cell: cellBooleanFormatter(), - }, - { - name: 'APN Cert Expiry', - selector: (row) => row['ApnCertExpiry'], - sortable: true, - exportSelector: 'ApnCertExpiry', - cell: cellBooleanFormatter(), - }, - { - name: 'VPP Token Expiry', - selector: (row) => row['VppTokenExpiry'], - sortable: true, - exportSelector: 'VppTokenExpiry', - cell: cellBooleanFormatter(), - }, - { - name: 'DEP Token Expiry', - selector: (row) => row['DepTokenExpiry'], - sortable: true, - exportSelector: 'DepTokenExpiry', - cell: cellBooleanFormatter(), - }, - { - name: 'No CA Config', - selector: (row) => row['NoCAConfig'], - sortable: true, - exportSelector: 'NoCAConfig', - cell: cellBooleanFormatter(), - }, - { - name: 'Sec Defaults Auto-Enable', - selector: (row) => row['SecDefaultsUpsell'], - sortable: true, - exportSelector: 'SecDefaultsUpsell', - cell: cellBooleanFormatter(), - }, - { - name: 'Sharepoint Quota', - selector: (row) => row['SharepointQuota'], - sortable: true, - exportSelector: 'SharepointQuota', - cell: cellBooleanFormatter(), - }, - { - name: 'Expiring Licenses', - selector: (row) => row['ExpiringLicenses'], - sortable: true, - exportSelector: 'ExpiringLicenses', - cell: cellBooleanFormatter(), + cell: (row) => CellTip(row['tenantId']), + exportSelector: 'tenantId', }, { name: 'Actions', - cell: Actions, + cell: Offcanvas, + maxWidth: '80px', }, ] - const tenant = useSelector((state) => state.app.currentTenant) - + const initialValues = currentlySelectedAlerts.filter((x) => x.tenantName === tenantDomain)[0] return ( -
    - {getResults.isFetching && ( - - Loading - - )} - {getResults.isSuccess && {getResults.data?.Results}} - {getResults.isError && ( - Could not connect to API: {getResults.error.message} - )} - - - - - + <> + + + + { + return ( + +

    + Classic Alerts are sent every 15 minutes, with a maximum of 1 unique alert + per 24 hours. These alerts do not use the Alert Rules Engine. +

    + {isLoadingCurrentAlerts && } + + + + {(props) => } + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + Set Alerts + {postResults.isFetching && ( + + )} + + + +
    + ) + }} + /> +
    +
    + + -
    -
    + columns, + reportName: `Scheduled-Jobs`, + path: `/api/ListAlertsQueue?RefreshGuid=${refreshState}`, + }} + /> + + + + ) } -export default ListAlertsQueue +export default ListClassicAlerts From c83128758230ba1de57de6c09680ef34444b7328 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 22 Dec 2023 14:58:37 +0100 Subject: [PATCH 24/51] Alert Queue improvements --- .../tenant/administration/ListAlertsQueue.jsx | 101 ++++++------------ 1 file changed, 34 insertions(+), 67 deletions(-) diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index 939a08a76d66..ef74024d7529 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -16,6 +16,34 @@ import 'react-datepicker/dist/react-datepicker.css' import { CippActionsOffcanvas, ModalService, TenantSelector } from 'src/components/utilities' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' import arrayMutators from 'final-form-arrays' +const alertsList = [ + { name: 'MFAAlertUsers', label: 'Alert on users without any form of MFA' }, + { name: 'MFAAdmins', label: 'Alert on admins without any form of MFA' }, + { + name: 'NoCAConfig', + label: + 'Alert on tenants without a Conditional Access policy, while having Conditional Access licensing available.', + }, + { name: 'AdminPassword', label: 'Alert on changed admin Passwords' }, + { name: 'QuotaUsed', label: 'Alert on 90% mailbox quota used' }, + { name: 'SharePointQuota', label: 'Alert on 90% SharePoint quota used' }, + { name: 'ExpiringLicenses', label: 'Alert on licenses expiring in 30 days' }, + { name: 'SecDefaultsUpsell', label: 'Alert on Security Defaults automatic enablement' }, + { + name: 'DefenderStatus', + label: 'Alert if Defender is not running (Tenant must be on-boarded in Lighthouse)', + }, + { + name: 'DefenderMalware', + label: 'Alert on Defender Malware found (Tenant must be on-boarded in Lighthouse)', + }, + { name: 'UnusedLicenses', label: 'Alert on unused licenses' }, + { name: 'OverusedLicenses', label: 'Alert on overused licenses' }, + { name: 'AppSecretExpiry', label: 'Alert on expiring application secrets' }, + { name: 'ApnCertExpiry', label: 'Alert on expiring APN certificates' }, + { name: 'VppTokenExpiry', label: 'Alert on expiring VPP tokens' }, + { name: 'DepTokenExpiry', label: 'Alert on expiring DEP tokens' }, +] const Offcanvas = (row, rowIndex, formatExtraData) => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() @@ -125,6 +153,7 @@ const ListClassicAlerts = () => { }, ] const initialValues = currentlySelectedAlerts.filter((x) => x.tenantName === tenantDomain)[0] + return ( <> @@ -153,73 +182,11 @@ const ListClassicAlerts = () => {
    - - - - - - - - - - - - - - - - - - - - - + {alertsList.map((alert, index) => ( + + + + ))}
    From 4ddf998c5521527d7bc96e1da8c65527f7e5d04c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Dec 2023 18:35:38 +0100 Subject: [PATCH 25/51] changes to classic alerts --- src/views/tenant/administration/ListAlertsQueue.jsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index ef74024d7529..92f1cec4fc5f 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { CButton, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' +import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' import { useSelector } from 'react-redux' import { Field, Form } from 'react-final-form' import { RFFCFormSwitch } from 'src/components/forms' @@ -124,7 +124,9 @@ const ListClassicAlerts = () => { }) values['tenantFilter'] = tenantDomain values['SetAlerts'] = true - genericPostRequest({ path: '/api/AddAlert', values: values }) + genericPostRequest({ path: '/api/AddAlert', values: values }).then((res) => { + setRefreshState(res.requestId) + }) } const { data: currentlySelectedAlerts = [], isLoading: isLoadingCurrentAlerts } = useGenericGetRequestQuery({ @@ -203,6 +205,11 @@ const ListClassicAlerts = () => { + {postResults.isSuccess && ( + +
  • {postResults.data.Results}
  • +
    + )}
    ) }} From 5a26c3fb327ca3dd1b94918cdb98a32130e1d48b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 27 Dec 2023 13:33:57 -0500 Subject: [PATCH 26/51] Remove default filter text --- src/views/cipp/CIPPSettings.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/views/cipp/CIPPSettings.jsx b/src/views/cipp/CIPPSettings.jsx index d8c19473d03d..e49cb27b0806 100644 --- a/src/views/cipp/CIPPSettings.jsx +++ b/src/views/cipp/CIPPSettings.jsx @@ -513,12 +513,6 @@ const GeneralSettings = () => { filter: 'Complex: Type eq Warning', }, ]} - defaultFilterText={ - GDAPResult.data.Results.GDAPIssues.filter((e) => e.Type === 'Error') - .length > 0 - ? 'Complex: Type eq Error' - : 'Complex: Type eq Warning' - } isModal={true} /> From 5485fe7f4beb73f1c80c67c68964b519fd2ec859 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Dec 2023 21:29:23 +0100 Subject: [PATCH 27/51] fix all tenants showing --- .../tenant/administration/ListAlertsQueue.jsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index 92f1cec4fc5f..4fa7bd38ccf8 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -155,6 +155,17 @@ const ListClassicAlerts = () => { }, ] const initialValues = currentlySelectedAlerts.filter((x) => x.tenantName === tenantDomain)[0] + const allTenantsAlert = currentlySelectedAlerts.find( + (tenant) => tenant.tenantName === 'AllTenants', + ) + function getLabel(item) { + if (typeof allTenantsAlert === 'object' && allTenantsAlert !== null) { + if (allTenantsAlert[`${item}`]) { + return `* Enabled via All Tenants` + } + } + return '' + } return ( @@ -186,7 +197,11 @@ const ListClassicAlerts = () => {
    {alertsList.map((alert, index) => ( - + ))} From 1132938716b475df30c5d87f1eb98a9854b24e69 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Dec 2023 14:27:08 +0100 Subject: [PATCH 28/51] Frontend changes --- .../tenant/administration/AlertRules.jsx | 114 +++--------------- 1 file changed, 20 insertions(+), 94 deletions(-) diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx index 1dd7d88b7f7d..8cbb4dab053c 100644 --- a/src/views/tenant/administration/AlertRules.jsx +++ b/src/views/tenant/administration/AlertRules.jsx @@ -30,6 +30,7 @@ import arrayMutators from 'final-form-arrays' const Offcanvas = (row, rowIndex, formatExtraData) => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() const [ocVisible, setOCVisible] = useState(false) + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const handleDeleteSchedule = (apiurl, message) => { ModalService.confirm({ @@ -58,7 +59,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { handleDeleteSchedule( - `/api/RemoveScheduledItem?&ID=${row.RowKey}`, + `/api/RemoveWebhookAlert?Tenantfilter=${tenantDomain}&ID=${row.RowKey}`, 'Do you want to delete this job?', ) } @@ -72,7 +73,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { setOCVisible(false)} @@ -85,7 +86,6 @@ const AlertRules = () => { const currentDate = new Date() const [startDate, setStartDate] = useState(currentDate) const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) - const [isArr, setisArr] = useState([]) const [refreshState, setRefreshState] = useState(false) const taskName = `Scheduled Task ${currentDate.toLocaleString()}` const { data: availableCommands = [], isLoading: isLoadingcmd } = useGenericGetRequestQuery({ @@ -93,33 +93,12 @@ const AlertRules = () => { }) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const onSubmit = (values) => { - const unixTime = Math.floor(startDate.getTime() / 1000) - const shippedValues = { - TenantFilter: tenantDomain, - Name: values.taskName, - Command: values.command, - Parameters: values.parameters, - ScheduledTime: unixTime, - Recurrence: values.Recurrence, - AdditionalProperties: values.additional, - PostExecution: { - Webhook: values.webhook, - Email: values.email, - PSA: values.psa, - }, - } - genericPostRequest({ path: '/api/AddScheduledItem', values: shippedValues }).then((res) => { + values['tenantfilter'] = tenantDomain + genericPostRequest({ path: '/api/AddAlert', values: values }).then((res) => { setRefreshState(res.requestId) }) } const columns = [ - { - name: 'Name', - selector: (row) => row['Name'], - sortable: true, - cell: (row) => CellTip(row['Name']), - exportSelector: 'Name', - }, { name: 'Tenant', selector: (row) => row['Tenant'], @@ -128,53 +107,18 @@ const AlertRules = () => { exportSelector: 'Tenant', }, { - name: 'Task State', - selector: (row) => row['TaskState'], + name: 'If', + selector: (row) => row['if'], sortable: true, cell: cellBadgeFormatter(), - exportSelector: 'TaskState', - }, - { - name: 'Command', - selector: (row) => row['Command'], - sortable: true, - cell: (row) => CellTip(row['Command']), - exportSelector: 'Command', - }, - { - name: 'Parameters', - selector: (row) => row['Parameters'], - sortable: true, - cell: (row) => CellTip(row['Parameters']), - exportSelector: 'Parameters', - }, - { - name: 'Scheduled Time', - selector: (row) => row['ScheduledTime'], - sortable: true, - cell: cellDateFormatter({ format: 'relative' }), - exportSelector: 'ScheduledTime', - }, - { - name: 'Last executed time', - selector: (row) => row['ExecutedTime'], - sortable: true, - cell: cellDateFormatter({ format: 'relative' }), - exportSelector: 'ExecutedTime', - }, - { - name: 'Recurrence', - selector: (row) => row['Recurrence'], - sortable: true, - cell: (row) => CellTip(row['Recurrence']), - exportSelector: 'Recurrence', + exportSelector: 'if', }, { - name: 'Sending to', - selector: (row) => row['PostExecution'], + name: 'Execute', + selector: (row) => row['execution'], sortable: true, - cell: (row) => CellTip(row['PostExecution']), - exportSelector: 'PostExecution', + cell: (row) => CellTip(row['execution']), + exportSelector: 'execution', }, { name: 'Actions', @@ -185,7 +129,7 @@ const AlertRules = () => { const ifvalues = [ { value: 'New-InboxRule', label: 'A new Inbox rule is created' }, - { value: 'Set-InboxRule', label: 'A existing Inbox rule is created' }, + { value: 'Set-InboxRule', label: 'A existing Inbox rule is edited' }, { value: 'Add member to role.', label: 'A user has been added to an admin role', @@ -220,7 +164,7 @@ const AlertRules = () => { }, { value: 'Add service principal', - label: 'A service prinicipal has been created', + label: 'A service principal has been created', }, { value: 'Remove service principal.', @@ -285,7 +229,7 @@ const AlertRules = () => { {i === 0 ? 'If' : 'And'} - + {ifCount > 1 && ( @@ -312,8 +256,8 @@ const AlertRules = () => { - - + + @@ -524,7 +468,7 @@ const AlertRules = () => { allTenants: true, helpContext: 'https://google.com', }} - title="Scheduled Tasks" + title="Alert Rules" tenantSelector={false} datatable={{ tableProps: { @@ -533,33 +477,15 @@ const AlertRules = () => { { label: 'Delete task', modal: true, - modalUrl: `/api/RemoveScheduledItem?&ID=!RowKey`, + modalUrl: `/api/RemoveWebhookAlert?Tenantfiler=!Tenant&ID=!RowKey`, modalMessage: 'Do you want to delete this job?', }, ], }, - filterlist: [ - { - filterName: 'Planned Jobs', - filter: 'Complex: TaskState eq Planned', - }, - { - filterName: 'Completed Jobs', - filter: 'Complex: TaskState eq Completed', - }, - { - filterName: 'Recurring Jobs', - filter: 'Complex: Recurrence gt 0', - }, - { - filterName: 'One-time Jobs', - filter: 'Complex: Recurrence eq 0', - }, - ], keyField: 'id', columns, reportName: `Scheduled-Jobs`, - path: `/api/ListScheduledItems?RefreshGuid=${refreshState}`, + path: `/api/ListWebhookAlert?RefreshGuid=${refreshState}`, }} /> From bd8d6bf3496120cbc83b8e237bac0733b0ea2dd7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Dec 2023 15:41:09 +0100 Subject: [PATCH 29/51] interface improvements --- src/views/cipp/Scheduler.jsx | 119 ++++++++++-------- .../tenant/administration/AlertRules.jsx | 117 +++++++++-------- 2 files changed, 133 insertions(+), 103 deletions(-) diff --git a/src/views/cipp/Scheduler.jsx b/src/views/cipp/Scheduler.jsx index 77c07163feb2..dc886acac1aa 100644 --- a/src/views/cipp/Scheduler.jsx +++ b/src/views/cipp/Scheduler.jsx @@ -27,61 +27,65 @@ import { ModalService, TenantSelector } from 'src/components/utilities' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' import arrayMutators from 'final-form-arrays' -const Offcanvas = (row, rowIndex, formatExtraData) => { +const Scheduler = () => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() - const [ocVisible, setOCVisible] = useState(false) - const handleDeleteSchedule = (apiurl, message) => { - ModalService.confirm({ - title: 'Confirm', - body:
    {message}
    , - onConfirm: () => ExecuteGetRequest({ path: apiurl }), - confirmLabel: 'Continue', - cancelLabel: 'Cancel', - }) - } - let jsonResults - try { - jsonResults = JSON.parse(row.Results) - } catch (error) { - jsonResults = row.Results - } + const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) - return ( - <> - - setOCVisible(true)}> - - - - - - handleDeleteSchedule( - `/api/RemoveScheduledItem?&ID=${row.RowKey}`, - 'Do you want to delete this job?', - ) - } - size="sm" - variant="ghost" - color="danger" - > - - - - setOCVisible(false)} - /> - - ) -} + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => + ExecuteGetRequest({ path: apiurl }).then((res) => { + setRefreshState(res.requestId) + }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row.Results) + } catch (error) { + jsonResults = row.Results + } + + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveScheduledItem?&ID=${row.RowKey}`, + 'Do you want to delete this job?', + ) + } + size="sm" + variant="ghost" + color="danger" + > + + + + setOCVisible(false)} + /> + + ) + } -const Scheduler = () => { const currentDate = new Date() const [startDate, setStartDate] = useState(currentDate) const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) @@ -371,6 +375,19 @@ const Scheduler = () => {
  • {postResults.data.Results}
  • )} + {getResults.isFetching && ( + + Loading + + )} + {getResults.isSuccess && ( + {getResults.data?.Results} + )} + {getResults.isError && ( + + Could not connect to API: {getResults.error.message} + + )}
    ) }} diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx index 8cbb4dab053c..cbb4017f4f76 100644 --- a/src/views/tenant/administration/AlertRules.jsx +++ b/src/views/tenant/administration/AlertRules.jsx @@ -27,62 +27,62 @@ import { ModalService, TenantSelector } from 'src/components/utilities' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' import arrayMutators from 'final-form-arrays' -const Offcanvas = (row, rowIndex, formatExtraData) => { +const AlertRules = () => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() - const [ocVisible, setOCVisible] = useState(false) - const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) - const handleDeleteSchedule = (apiurl, message) => { - ModalService.confirm({ - title: 'Confirm', - body:
    {message}
    , - onConfirm: () => ExecuteGetRequest({ path: apiurl }), - confirmLabel: 'Continue', - cancelLabel: 'Cancel', - }) - } - let jsonResults - try { - jsonResults = JSON.parse(row.Results) - } catch (error) { - jsonResults = row.Results - } + const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) - return ( - <> - - setOCVisible(true)}> - - - - - - handleDeleteSchedule( - `/api/RemoveWebhookAlert?Tenantfilter=${tenantDomain}&ID=${row.RowKey}`, - 'Do you want to delete this job?', - ) - } - size="sm" - variant="ghost" - color="danger" - > - - - - setOCVisible(false)} - /> - - ) -} + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => ExecuteGetRequest({ path: apiurl }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row.Results) + } catch (error) { + jsonResults = row.Results + } -const AlertRules = () => { + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveWebhookAlert?Tenantfilter=${tenantDomain}&ID=${row.RowKey}`, + 'Do you want to delete this job?', + ) + } + size="sm" + variant="ghost" + color="danger" + > + + + + setOCVisible(false)} + /> + + ) + } const currentDate = new Date() const [startDate, setStartDate] = useState(currentDate) const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) @@ -455,6 +455,19 @@ const AlertRules = () => {
  • {postResults.data.Results}
  • )} + {getResults.isFetching && ( + + Loading + + )} + {getResults.isSuccess && ( + {getResults.data?.Results} + )} + {getResults.isError && ( + + Could not connect to API: {getResults.error.message} + + )} ) }} From 68e8eae650e9e806dc1fcdce4617cbec67cb49a8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Dec 2023 15:43:26 +0100 Subject: [PATCH 30/51] interface improvements --- .../tenant/administration/ListAlertsQueue.jsx | 141 ++++++++++-------- 1 file changed, 79 insertions(+), 62 deletions(-) diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index 4fa7bd38ccf8..b0800dfaa7bf 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -45,73 +45,77 @@ const alertsList = [ { name: 'DepTokenExpiry', label: 'Alert on expiring DEP tokens' }, ] -const Offcanvas = (row, rowIndex, formatExtraData) => { +const ListClassicAlerts = () => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() - const [ocVisible, setOCVisible] = useState(false) - const handleDeleteSchedule = (apiurl, message) => { - ModalService.confirm({ - title: 'Confirm', - body:
    {message}
    , - onConfirm: () => ExecuteGetRequest({ path: apiurl }), - confirmLabel: 'Continue', - cancelLabel: 'Cancel', - }) - } - let jsonResults - try { - jsonResults = JSON.parse(row) - } catch (error) { - jsonResults = row - } + const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) - return ( - <> - - setOCVisible(true)}> - - - - - - handleDeleteSchedule( - `/api/RemoveQueuedAlert?&ID=${row.tenantId}`, - 'Do you want to delete the queued alert?', - ) - } - size="sm" - variant="ghost" - color="danger" - > - - - - ({ - label: key, - value: - typeof row[key] === 'boolean' ? ( - row[key] ? ( - - ) : ( - + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => + ExecuteGetRequest({ path: apiurl }).then((res) => { + setRefreshState(res.requestId) + }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row) + } catch (error) { + jsonResults = row + } + + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveQueuedAlert?&ID=${row.tenantId}`, + 'Do you want to delete the queued alert?', ) - ) : ( - row[key] - ), - }))} - placement="end" - visible={ocVisible} - id={row.id} - hideFunction={() => setOCVisible(false)} - /> - - ) -} + } + size="sm" + variant="ghost" + color="danger" + > + + + + ({ + label: key, + value: + typeof row[key] === 'boolean' ? ( + row[key] ? ( + + ) : ( + + ) + ) : ( + row[key] + ), + }))} + placement="end" + visible={ocVisible} + id={row.id} + hideFunction={() => setOCVisible(false)} + /> + + ) + } -const ListClassicAlerts = () => { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const [refreshState, setRefreshState] = useState(false) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() @@ -225,6 +229,19 @@ const ListClassicAlerts = () => {
  • {postResults.data.Results}
  • )} + {getResults.isFetching && ( + + Loading + + )} + {getResults.isSuccess && ( + {getResults.data?.Results} + )} + {getResults.isError && ( + + Could not connect to API: {getResults.error.message} + + )} ) }} From c593c2db814ffb5fe2b2b3336e2449391907c06a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 Dec 2023 10:19:35 -0500 Subject: [PATCH 31/51] CellGenericFormat - Add target="_blank" --- src/components/tables/CellGenericFormat.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/tables/CellGenericFormat.jsx b/src/components/tables/CellGenericFormat.jsx index 7bb0236d0030..d4c7a7cfbf41 100644 --- a/src/components/tables/CellGenericFormat.jsx +++ b/src/components/tables/CellGenericFormat.jsx @@ -44,7 +44,11 @@ export const cellGenericFormatter = return {CellTip('Failed to retrieve from API')} } if (cell.toLowerCase().startsWith('http')) { - return
    URL + return ( + + URL + + ) } return CellTip(cell) } From 25d5b004e4ea296786888ffb6a01d02b911dd277 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Dec 2023 18:24:35 +0100 Subject: [PATCH 32/51] added formatter for celltable to allow different colours --- src/components/tables/CellTable.jsx | 14 +++++++++----- src/views/cipp/CIPPSettings.jsx | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/tables/CellTable.jsx b/src/components/tables/CellTable.jsx index c9a13391bebe..e8d31ced8256 100644 --- a/src/components/tables/CellTable.jsx +++ b/src/components/tables/CellTable.jsx @@ -2,7 +2,7 @@ import React from 'react' import { CButton } from '@coreui/react' import { ModalService } from '../utilities' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons' // 1. Import the required FontAwesome icon +import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons' import { cellGenericFormatter } from './CellGenericFormat' export default function cellTable( @@ -11,6 +11,7 @@ export default function cellTable( propertyName, checkWhenZero = false, crossWhenZero = false, + dangerButton = false, // Added 4th parameter for btn-danger class ) { const handleTable = ({ row }) => { const QueryColumns = [] @@ -34,7 +35,7 @@ export default function cellTable( size: 'lg', }) } - //if the row propertyName is a bool, then return a check or cross + if (typeof row[propertyName] === 'boolean') { if (row[propertyName]) { return @@ -55,15 +56,18 @@ export default function cellTable( return } + // Use dangerButton to determine button class + const buttonClassName = dangerButton ? 'btn-danger' : '' + return ( - handleTable({ row })}> + handleTable({ row })}> {row[propertyName].length} Items ) } export const cellTableFormatter = - (propertyName, checkWhenZero = false, crossWhenZero = false) => + (propertyName, checkWhenZero = false, crossWhenZero = false, dangerButton = false) => (row, index, column, id) => { - return cellTable(row, column, propertyName, checkWhenZero, crossWhenZero) + return cellTable(row, column, propertyName, checkWhenZero, crossWhenZero, dangerButton) } diff --git a/src/views/cipp/CIPPSettings.jsx b/src/views/cipp/CIPPSettings.jsx index e49cb27b0806..4c74b28bbcc9 100644 --- a/src/views/cipp/CIPPSettings.jsx +++ b/src/views/cipp/CIPPSettings.jsx @@ -223,7 +223,7 @@ const GeneralSettings = () => { { name: 'Missing GDAP Roles', selector: (row) => row?.MissingRoles, - cell: cellTableFormatter('MissingRoles', true, false), + cell: cellTableFormatter('MissingRoles', true, false, true), }, { name: 'Roles available', From dd7ec35b231a485c275e779ac1a2ffd57eaa5a3c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 30 Dec 2023 17:32:51 +0100 Subject: [PATCH 33/51] updates to alerts --- .../tenant/administration/AlertRules.jsx | 124 ++++++++++-------- 1 file changed, 69 insertions(+), 55 deletions(-) diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx index cbb4017f4f76..36dc3236f7e8 100644 --- a/src/views/tenant/administration/AlertRules.jsx +++ b/src/views/tenant/administration/AlertRules.jsx @@ -179,7 +179,10 @@ const AlertRules = () => { label: 'A user has logged in from a known bad-reputation IP', }, { value: 'customField', label: 'Custom Log Query' }, + { value: 'anyAlert', label: 'Any alert has been received' }, ] + + const customIfValues = [{ value: 'customField', label: 'Custom Log Query' }] const dovalues = [ { value: 'cippcommand', label: 'Execute a CIPP Command' }, { value: 'becremediate', label: 'Execute a BEC Remediate' }, @@ -229,7 +232,10 @@ const AlertRules = () => { {i === 0 ? 'If' : 'And'} - + {ifCount > 1 && ( @@ -308,7 +314,8 @@ const AlertRules = () => { value: cmd.Function, name: cmd.Function, }))} - name={`command`} + name={`do.${i}.command`} + key={`do.${i}.command`} placeholder={ isLoadingcmd ? ( @@ -324,7 +331,11 @@ const AlertRules = () => { - + @@ -377,61 +388,64 @@ const AlertRules = () => { {/* eslint-disable react/prop-types */} {(props) => { - const selectedCommand = availableCommands.find( - (cmd) => cmd.Function === props.values.command?.value, - ) - let paramblock = null - if (selectedCommand) { - //if the command parameter type is boolean we use else . - const parameters = selectedCommand.Parameters - if (parameters.length > 0) { - paramblock = parameters.map((param, idx) => ( - - - - {param.Type === 'System.Boolean' || - param.Type === - 'System.Management.Automation.SwitchParameter' ? ( - <> - - - - ) : ( - <> - {param.Type === 'System.Collections.Hashtable' ? ( - + return props.values.do?.map((command, commandIndex) => { + const selectedCommand = availableCommands.find( + (cmd) => cmd.Function === command.command?.value, + ) + let paramblock = null + if (selectedCommand) { + const parameters = selectedCommand.Parameters + if (parameters.length > 0) { + paramblock = parameters.map((param, idx) => ( + <> + + + + {param.Type === 'System.Boolean' || + param.Type === + 'System.Management.Automation.SwitchParameter' ? ( + <> + + + ) : ( - + <> + {param.Type === 'System.Collections.Hashtable' ? ( + + ) : ( + + )} + )} - - )} - - - - )) + + + + + )) + } } - } - return paramblock + return paramblock + }) }} From 8e1e152cbd8b5bae4f215767c39897a96bf23902 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 30 Dec 2023 22:01:10 +0100 Subject: [PATCH 34/51] added and fixed fields --- src/views/tenant/administration/AlertRules.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx index 36dc3236f7e8..65dd8cfe7029 100644 --- a/src/views/tenant/administration/AlertRules.jsx +++ b/src/views/tenant/administration/AlertRules.jsx @@ -176,7 +176,11 @@ const AlertRules = () => { }, { value: 'badRepIP', - label: 'A user has logged in from a known bad-reputation IP', + label: 'A user has logged in a using a known VPN, Proxy, Or anonymizer', + }, + { + value: 'HostedIP', + label: 'A user has logged in a using a known hosting provider IP', }, { value: 'customField', label: 'Custom Log Query' }, { value: 'anyAlert', label: 'Any alert has been received' }, From d8f38f18fd1a9276214a998849dd5185be31e3d9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 30 Dec 2023 23:13:37 +0100 Subject: [PATCH 35/51] interface buggo --- src/views/tenant/administration/AlertRules.jsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx index 65dd8cfe7029..a06bcb67481d 100644 --- a/src/views/tenant/administration/AlertRules.jsx +++ b/src/views/tenant/administration/AlertRules.jsx @@ -26,6 +26,7 @@ import TenantListSelector from 'src/components/utilities/TenantListSelector' import { ModalService, TenantSelector } from 'src/components/utilities' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' import arrayMutators from 'final-form-arrays' +import countryList from 'src/data/countryList.json' const AlertRules = () => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() @@ -160,7 +161,7 @@ const AlertRules = () => { }, { value: 'UserLoggedInFromUnknownLocation', - label: 'A user has logged in from a location ', + label: 'A user has logged in from a location not in the allowed locations list', }, { value: 'Add service principal', @@ -269,6 +270,17 @@ const AlertRules = () => { + + ({ + value: Code, + name: Name, + }))} + /> + , From f276c922f37c90b567bb25fb9d36127aec586eec Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 31 Dec 2023 00:15:13 +0100 Subject: [PATCH 36/51] fix delete --- src/views/tenant/administration/AlertRules.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx index a06bcb67481d..b505508703d9 100644 --- a/src/views/tenant/administration/AlertRules.jsx +++ b/src/views/tenant/administration/AlertRules.jsx @@ -62,7 +62,7 @@ const AlertRules = () => { handleDeleteSchedule( - `/api/RemoveWebhookAlert?Tenantfilter=${tenantDomain}&ID=${row.RowKey}`, + `/api/RemoveWebhookAlert?Tenantfilter=${row.Tenant}&ID=${row.RowKey}`, 'Do you want to delete this job?', ) } @@ -520,7 +520,7 @@ const AlertRules = () => { { label: 'Delete task', modal: true, - modalUrl: `/api/RemoveWebhookAlert?Tenantfiler=!Tenant&ID=!RowKey`, + modalUrl: `/api/RemoveWebhookAlert?Tenantfilter=!Tenant&ID=!RowKey`, modalMessage: 'Do you want to delete this job?', }, ], From af85624c653a583054b9e0ce6672dc3b454ceb2f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 1 Jan 2024 12:31:59 -0500 Subject: [PATCH 37/51] CellTable improvement Add ability to display single object as Name/Value columns --- src/components/tables/CellGenericFormat.jsx | 4 ++- src/components/tables/CellTable.jsx | 38 ++++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/components/tables/CellGenericFormat.jsx b/src/components/tables/CellGenericFormat.jsx index d4c7a7cfbf41..465a7dd25f37 100644 --- a/src/components/tables/CellGenericFormat.jsx +++ b/src/components/tables/CellGenericFormat.jsx @@ -7,6 +7,7 @@ import { } from '@fortawesome/free-solid-svg-icons' import { CBadge, CTooltip } from '@coreui/react' import CellBoolean from 'src/components/tables/CellBoolean.jsx' +import cellTable from './CellTable' const IconWarning = () => const IconError = () => @@ -56,6 +57,7 @@ export const cellGenericFormatter = return {CellTip(cell)} } if (Array.isArray(cell) || typeof cell === 'object') { - return CellTip(JSON.stringify(cell)) + //return CellTip(JSON.stringify(cell)) + return cellTable(row, cell) } } diff --git a/src/components/tables/CellTable.jsx b/src/components/tables/CellTable.jsx index e8d31ced8256..c8720b9e0f00 100644 --- a/src/components/tables/CellTable.jsx +++ b/src/components/tables/CellTable.jsx @@ -13,9 +13,23 @@ export default function cellTable( crossWhenZero = false, dangerButton = false, // Added 4th parameter for btn-danger class ) { - const handleTable = ({ row }) => { + var columnProp = '' + if (propertyName) { + columnProp = row[propertyName] + } else { + columnProp = column + } + if (!Array.isArray(columnProp) && typeof columnProp === 'object') { + columnProp = Object.entries(columnProp).map((row) => { + return { Name: row[0], Value: row[1] } + }) + } + console.log(Array.isArray(columnProp)) + + const handleTable = ({ columnProp }) => { const QueryColumns = [] - const columns = Object.keys(row[propertyName][0]).map((key) => { + + const columns = Object.keys(columnProp[0]).map((key) => { QueryColumns.push({ name: key, selector: (row) => row[key], @@ -24,8 +38,9 @@ export default function cellTable( cell: cellGenericFormatter(), }) }) + ModalService.open({ - data: row[propertyName], + data: columnProp, componentType: 'table', componentProps: { columns: QueryColumns, @@ -36,15 +51,15 @@ export default function cellTable( }) } - if (typeof row[propertyName] === 'boolean') { - if (row[propertyName]) { + if (typeof columnProp === 'boolean') { + if (columnProp) { return } return } - if (!row[propertyName] || !Array.isArray(row[propertyName]) || row[propertyName].length === 0) { - if (row[propertyName] === undefined) { + if (!columnProp || !Array.isArray(columnProp) || columnProp.length === 0) { + if (columnProp === undefined) { return } if (checkWhenZero) { @@ -60,8 +75,13 @@ export default function cellTable( const buttonClassName = dangerButton ? 'btn-danger' : '' return ( - handleTable({ row })}> - {row[propertyName].length} Items + handleTable({ columnProp })} + > + {columnProp.length} Items ) } From e5a4281acd25f3a41e3c75fa7e090a7696512e14 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 1 Jan 2024 19:14:08 -0500 Subject: [PATCH 38/51] GraphExplorer update Replace old API endpoint Update form components and querying --- src/components/tables/CellTable.jsx | 9 +- .../tenant/administration/GraphExplorer.jsx | 257 +++++++++++------- 2 files changed, 167 insertions(+), 99 deletions(-) diff --git a/src/components/tables/CellTable.jsx b/src/components/tables/CellTable.jsx index c8720b9e0f00..f62baec9b488 100644 --- a/src/components/tables/CellTable.jsx +++ b/src/components/tables/CellTable.jsx @@ -19,12 +19,18 @@ export default function cellTable( } else { columnProp = column } + if (!Array.isArray(columnProp) && typeof columnProp === 'object') { columnProp = Object.entries(columnProp).map((row) => { return { Name: row[0], Value: row[1] } }) + } else if (Array.isArray(columnProp) && Object.entries(columnProp).length === 1) { + columnProp = columnProp.map((row) => { + return { + Value: row, + } + }) } - console.log(Array.isArray(columnProp)) const handleTable = ({ columnProp }) => { const QueryColumns = [] @@ -73,7 +79,6 @@ export default function cellTable( // Use dangerButton to determine button class const buttonClassName = dangerButton ? 'btn-danger' : '' - return ( { - let navigate = useNavigate() const tenant = useSelector((state) => state.app.currentTenant) let query = useQuery() - const endpoint = query.get('endpoint') - const disablePagination = query.get('disablePagination') - const SearchNow = query.get('SearchNow') + const [params, setParams] = useState() + const [random, setRandom] = useState('') + const [searchNow, setSearchNow] = useState(false) const [visibleA, setVisibleA] = useState(true) const handleSubmit = async (values) => { - setVisibleA(false) - - const shippedValues = { - tenantFilter: tenant.defaultDomainName, - SearchNow: true, - endpoint: encodeURIComponent(values.endpoint), - random: (Math.random() + 1).toString(36).substring(7), - } - var queryString = Object.keys(shippedValues) - .map((key) => key + '=' + shippedValues[key]) - .join('&') - - navigate(`?${queryString}`) + setParams(values) + setRandom((Math.random() + 1).toString(36).substring(7)) + setSearchNow(true) } const [execGraphRequest, graphrequest] = useLazyGenericGetRequestQuery() const QueryColumns = { set: false, data: [] } if (graphrequest.isSuccess) { - if (graphrequest.data.length === 0) { + if (graphrequest.data.Results.length === 0) { graphrequest.data = [{ data: 'No Data Found' }] } //set columns - - const flatObj = Object.keys(graphrequest.data[0]).flat(100) - flatObj.map((value) => + Object.keys(graphrequest.data.Results[0]).map((value) => QueryColumns.data.push({ name: value, selector: (row) => row[`${value.toString()}`], @@ -71,16 +56,109 @@ const GraphExplorer = () => { QueryColumns.set = true } - useEffect(() => { - execGraphRequest({ - path: 'api/execGraphRequest', + const presets = [ + { + label: 'All users with email addresses', + value: '6164e239-0c9a-4a27-9049-6250bf65a3e3', + params: { endpoint: '/users', $select: 'userprincipalname,mail,proxyAddresses', $filter: '' }, + }, + { + label: 'All Devices listing ID, Displayname, and registration status', + value: 'e7fdc49a-72a9-4a70-9dbf-a74152495d80', + params: { + endpoint: '/devices', + $select: 'deviceId,DisplayName,profileType,registrationDateTime,trustType', + $filter: '', + }, + }, + { + label: 'All contacts and their mail addresses', + value: 'f1844e3d-cb3e-4611-9bab-f5f42169bcd0', + params: { + endpoint: '/contacts', + $select: 'CompanyName,DisplayName,Mail,ProxyAddresses', + $filter: '', + }, + }, + { + label: 'Outlook Agents used in last 90 days', + value: 'eea3cacb-ca95-4998-bcd9-ff1815a7a493', params: { - tenantFilter: tenant.defaultDomainName, - endpoint: endpoint, - disablePagination: disablePagination, + endpoint: `reports/getEmailAppUsageUserDetail(period='D90')`, + $format: 'application/json', + $filter: '', + $select: '', }, - }) - }, [endpoint, execGraphRequest, tenant.defaultDomainName, query, disablePagination]) + }, + { + label: 'Activated M365 Subscription installations', + value: 'ccbe5b0d-ff0d-4262-a789-ccbd8f8d52e1', + params: { + endpoint: '/reports/getOffice365ActivationsUserDetail', + $format: 'application/json', + $filter: '', + $select: '', + }, + }, + { + label: 'Applications signed in in last 30 days', + value: 'a9ec9f2d-c102-4b4f-9b9d-c2bc57155990', + params: { + endpoint: `reports/getAzureADApplicationSignInSummary(period='D30')`, + $filter: '', + $select: '', + }, + }, + { + label: 'User Registration Report', + value: 'a00d251d-2743-484a-b8bb-8601199ceb68', + params: { + endpoint: '/reports/authenticationMethods/userRegistrationDetails', + $filter: '', + $select: '', + }, + }, + { + label: 'All Global Admins', + value: 'c73df2bb-08fe-4fb2-b112-97006bdcf0a8', + params: { + endpoint: 'directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members', + $filter: '', + $select: '', + }, + }, + { + label: 'Multifactor Authentication Report for Admins', + value: 'ae7b3dc4-822b-46b9-aa0a-0305b4c33632', + params: { + endpoint: '/reports/authenticationMethods/userRegistrationDetails', + $filter: 'IsAdmin eq true', + $select: '', + }, + }, + { + label: 'Secure Score with Current Score and Max Score', + value: 'bd6665e8-02c1-4cbf-bd3c-d2a52f17c2cf', + params: { + endpoint: 'security/secureScores', + $top: 90, + $select: 'currentscore,maxscore,activeusercount,enabledservices', + $filter: '', + }, + }, + ] + + useEffect(() => { + if (params?.endpoint) { + execGraphRequest({ + path: 'api/ListGraphRequest', + params: { + ...params, + random: random, + }, + }) + } + }, [params, execGraphRequest, tenant.defaultDomainName, random]) const WhenFieldChanges = ({ field, set }) => ( @@ -92,8 +170,10 @@ const GraphExplorer = () => { {({ form }) => ( {(value) => { - let template = value - onChange(template) + let preset = presets.filter(function (obj) { + return obj.value === value + }) + onChange(preset[0]?.params[set]) }} )} @@ -101,7 +181,6 @@ const GraphExplorer = () => { )} ) - WhenFieldChanges.propTypes = { field: PropTypes.node, set: PropTypes.string, @@ -131,85 +210,69 @@ const GraphExplorer = () => { { return ( - + + + + + - - + + + + + + + + + - - - - @@ -230,8 +293,8 @@ const GraphExplorer = () => {
    - {!SearchNow && Execute a search to get started.} - {graphrequest.isSuccess && QueryColumns.set && SearchNow && ( + {!searchNow && Execute a search to get started.} + {graphrequest.isSuccess && QueryColumns.set && searchNow && ( Results @@ -241,7 +304,7 @@ const GraphExplorer = () => { reportName="GraphExplorer" dynamicColumns={false} columns={QueryColumns.data} - data={graphrequest.data} + data={graphrequest.data.Results} isFetching={graphrequest.isFetching} />
    From a9acf15eb8cd8f003562d51c904a264a15efcf0d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 1 Jan 2024 19:20:50 -0500 Subject: [PATCH 39/51] Update GraphExplorer.jsx --- src/views/tenant/administration/GraphExplorer.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/tenant/administration/GraphExplorer.jsx b/src/views/tenant/administration/GraphExplorer.jsx index c95e3b03f93c..dc0815e22f04 100644 --- a/src/views/tenant/administration/GraphExplorer.jsx +++ b/src/views/tenant/administration/GraphExplorer.jsx @@ -144,6 +144,7 @@ const GraphExplorer = () => { $top: 90, $select: 'currentscore,maxscore,activeusercount,enabledservices', $filter: '', + NoPagination: true, }, }, ] @@ -227,7 +228,9 @@ const GraphExplorer = () => { values={presets} /> + + Date: Tue, 2 Jan 2024 00:19:10 -0500 Subject: [PATCH 40/51] GraphHelper Presets and bugfixes add custom preset support bugfixes --- src/components/forms/RFFComponents.jsx | 23 +- .../tenant/administration/GraphExplorer.jsx | 215 +++++++++++++++--- 2 files changed, 200 insertions(+), 38 deletions(-) diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 5dffcde46dac..4da57f18e54c 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -299,6 +299,7 @@ export const RFFCFormSelect = ({ className = 'mb-3', validate, disabled = false, + props, }) => { // handler for ignoring the first element ('the placeholder') const selectValidate = (value, allValues, meta) => { @@ -320,10 +321,11 @@ export const RFFCFormSelect = ({ valid={!meta.error && meta.touched} invalid={meta.error && meta.touched} disabled={disabled} + {...props} > - {values.map(({ label, value }, idx) => ( - ))} @@ -383,11 +385,14 @@ export const RFFSelectSearch = ({ disabled = false, retainInput = true, isLoading = false, + refreshFunction, + props, }) => { const [inputText, setInputText] = useState('') const selectSearchvalues = values.map((val) => ({ value: val.value, label: val.name, + ...val.props, })) const debounceOnInputChange = useMemo(() => { @@ -410,7 +415,16 @@ export const RFFSelectSearch = ({ {({ meta, input }) => { return (
    - {label} + + {label} + {refreshFunction && ( + + + + + + )} + {onChange && (