From 0ff34d105de4664a8cc936e7b8bff39d92f3b641 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Sat, 22 Feb 2025 10:37:59 -0800 Subject: [PATCH] Inline secret creation (#207) --- packages/schemas/src/v3/connection.schema.ts | 1 + packages/schemas/src/v3/github.schema.ts | 1 + packages/web/package.json | 1 + packages/web/public/github_pat_creation.png | Bin 0 -> 73849 bytes packages/web/src/actions.ts | 18 +- .../app/[domain]/components/configEditor.tsx | 182 +++++--- .../secretCombobox.tsx | 415 ++++++++++++++++++ .../sharedConnectionCreationForm.tsx | 117 ++++- .../[id]/components/configSetting.tsx | 114 ++++- .../app/[domain]/connections/[id]/page.tsx | 14 +- .../[domain]/connections/new/[type]/page.tsx | 2 +- .../{quickActions.ts => quickActions.tsx} | 70 ++- packages/web/src/app/globals.css | 11 + .../app/onboard/components/orgCreateForm.tsx | 1 - packages/web/src/components/ui/popover.tsx | 31 ++ packages/web/src/lib/strings.ts | 7 + packages/web/src/lib/utils.ts | 11 + schemas/v3/github.json | 5 + yarn.lock | 21 + 19 files changed, 905 insertions(+), 117 deletions(-) create mode 100644 packages/web/public/github_pat_creation.png create mode 100644 packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx rename packages/web/src/app/[domain]/connections/{quickActions.ts => quickActions.tsx} (65%) create mode 100644 packages/web/src/components/ui/popover.tsx create mode 100644 packages/web/src/lib/strings.ts diff --git a/packages/schemas/src/v3/connection.schema.ts b/packages/schemas/src/v3/connection.schema.ts index 858a28db..de1a5c70 100644 --- a/packages/schemas/src/v3/connection.schema.ts +++ b/packages/schemas/src/v3/connection.schema.ts @@ -62,6 +62,7 @@ const schema = { "type": "string", "pattern": "^[\\w.-]+$" }, + "default": [], "examples": [ [ "my-org-name" diff --git a/packages/schemas/src/v3/github.schema.ts b/packages/schemas/src/v3/github.schema.ts index 2b6b2e83..412ea9d3 100644 --- a/packages/schemas/src/v3/github.schema.ts +++ b/packages/schemas/src/v3/github.schema.ts @@ -58,6 +58,7 @@ const schema = { "type": "string", "pattern": "^[\\w.-]+$" }, + "default": [], "examples": [ [ "my-org-name" diff --git a/packages/web/package.json b/packages/web/package.json index 93bd9aa0..b262b52b 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -50,6 +50,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.0", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.0", diff --git a/packages/web/public/github_pat_creation.png b/packages/web/public/github_pat_creation.png new file mode 100644 index 0000000000000000000000000000000000000000..e9295fca7ae48cb9d3486c160cd7985f23fef577 GIT binary patch literal 73849 zcmeFZWl&v9*Di{?6N0>mA$rwLV3kVHnnLjVH1C`N0EteVxVdi6;I6647!X zDpKsL>J)p|5bRJMW9*%moADWYCdLq!pBy*z)PMk=KUltDT=W)pbjB!)lF(@29 z3o}HKeWMhR!HY%t*qx#`wSVpN{1`0G92@*XO=HOHan8u27tnu{Ggl2cQ(|vlj)X9j%&Bodm zz~?4N_D2pr(DPd~Ga1PrS)8l{$+YB^NJQ-%O-ZoNXJ+N)Sp;*OSXVNUkmht%x@{otV}G-f7cBv zD)1J|r)247YOO71X#*NFP#Z$*Y#aiA_BsSO3>g{AZm1hy@L_5P|^n-$D~Ypf=~u0?CoUQcO_| zbO&kKAHN*XUmDQ$b_ZR7-a}{mKfu65z@)`KsJVe3r$c+alf>^g%?bUbc|`wXdyypfu(pF{c0Co%eUkF#mOw+`z%#-x(-{ z>OuW$=>F*r!&wvZKcRoMdOwGP1lC-kqD*P)@b);&K^I->t8 z@}DuP?A+t}C-kqDI}zb3J8!?rY?J*f@?R47AGi5e=pSY9!$$k$4}^~YCH`N#0}|lU ze^Z|S2LfD&^~Yv5P|1_ZRB5!|c!Ilkuw82hBnm}8!4`l#9hFYo@G z6zLEBGLScJ$^NGr!1X|72>WhSOiG4gEEsO+LuWL&QQ9mvtHLC6c%b!#;}5{C){T=i zd+p%$MG=MT}q!}z`F@};eQ1nXEP|gp z*z@j~D$MlyxFzu<5{l99xR|+LWj@%$Hq8ym+ZR9Tu;=B@KoNxB>_U4%b*-=NA!Vz zGBhV%Jy*3-nF{Yu^|~B~QEIu)FcFI{+}X4h8l?eC%)cRsH`~`O#^F8L!Qh@V`Kx|$ z12(Mw(%9_e&blM+V&^@f7rO4=7@;^0X)<5IEd~}uFG)7wYFqBZDF4Kgs~xTWq1tqX zB*^a8W!}(eH`K}4RwlkWJ&sC6>~!rVoHW=MF6exHd=fugG_MR)%dz+#mQz$`MxLT3+sNThr)!kozHAk2MWMw4A9 z67&!&98cpUlTRk&?F+|_CKC3M>mU{oGarAEhsUJMlfk0LF`6FS8P?oCiy?QqUUc?& z+(o98i3?#f9TCB0(^4Gwc)VdGW{q6huEGZfN{9$h_z0mU(sQRNf zbl+FIMy2|*bYx|@f6T0q3wXYKtVC}NWv>pvS)Hi;&&9(iRpb-2 z(Cp`S^o}NUaE(o7EQ(nl=S1))e(SdXxxFh2|78|;(qe8}FT@)%5?)c0HXeEjnLiwMiF;t)2 z90*qNS+H)C-EPvQY78!;E_iV)F9|>EenkHuS2l}Gpp$=jd=Gdyx>~y+-0@9j=hyG4I2xF%So=M! zS&^q1_bY}`atSExMiYT-5bvBy*M}{^=OMr|a10w$yHA2E#%;Tr(6ASdi?-2cj>V)i zO@m^C_VNojs_VMnU$#{JY{DysVFV!We%wRr7d{0^^Gc_+1@yH<IDizgM<^I-swD+e}4v}a;`kF=xtNiwk>_trv_G3T~j zVXq`Gj0}4h#!xj*`a9NOJr-Z?OE-AiA`jOlZcl}?YWA2^MsOx*MISiW;7eK$ElV!B zbH*w{A6ndxn8Y8hFlCZR+V6-JS8guv_Rl+qH%KN^xQZ-4!OQqZJ1jpS}9=2|$ ztT^v(u~@*fY$9Z?Z4UT$r9v5|6tJky2#r%PC;EFU-$`k$oa6I06m9c04~ZIIy{$|W z;4^2d5v|V%#az&qQ;(wupCDg7#z(hOG_i(=J2UwUN`@9cL^*c|`ZLnaf>8sp4k8|l zZTa+1i{2}E@o>49%>hNij>n2C!>0l_=2aGBamp?}V@ys8Jup{U--b1&`fvHUi_BSU z2yJSN(!V5-$wl{I9g&h#PXdFZ2Q9G2Gf1FM(pzqeESF9vQBQ1#l1S;agSSGeR4vx* z>Tjnr(wA^fAdLap*3nC*OOwRC<`u?GDr!w%D?^@F1--74@M_WeYxH<(Mk$13 z!OWPosD!5;kZb4Yu!dFQ5lo%z8pb?PukvFH$9`1~?@?v)Wkx zz7J0{_2l(D{VMt-j71eo@9YY86 zwda4-GlZEWlT*M|0q%gQ4R#~0o%xk9Up6-EYy~L>xzM*mcSjw@;}!PleTA0n5tl9X zr7JbxR0?3k6PcOyTkVyv zJ}n|;@`#ZM=+s+C2)4L7H`duaqI_9wjSuLE9|$oLg-eG%{>wNEgn`JwxO2PL5wQAd z8pHewh+lNYCjrJYs>{JW=}0tYffB2ZCx-{LJgq4#_=>gjA=E4MR<~t292WH+9LAYZ zUYcPII9lrM$~^}GlpK}hU=o>3`Y!{d5f}TD<-AzGtUQgbfrxD~usP_trt=OWuFajO zAeACNWBs~VyeHBU1uGbR*jkt5@ft~WVT5z~iT}h{>1t{Hg%AD;+)nLHjzZqeN(D#<5hRbk!XJAE(W3|6=nz%%pJyJ|-`T2*7^e z#AWo@j~HW(DBGgxpDTB_k0e={oZIr)ciMd;nZk-0)${>OXJr>Z6YUO5ztvt32sS77 z+^_rS_eFp`j!}9ABVVL{n!DdR%Q~ZZmRJ6=T2wz%hPHP*0o@+o_-e#H;U|AnDnNU7 zVnX$`QL^IB?$MV@PWE6rpTEqYW)|+fpbiI}_62?z7JI?FXKW?|j#DEF--RFF0J zYC5?g!Z3R=g?+pxGc|7ujzK7^4Jtez~^N!}*x z@z>k@n7Sdft%Ut^>*J3m(TNW&B=P+L6<2;hF~mFIThFj%Rrftj3yxrm&t(D{PH->c zv1KmRgNq$AkTyEMWOiyY{%D6XP-EijJ&pMd!{ozV7rF0m&>V;iu99sn&3jmq(WeZL zypu@Rm#9|lM%sOTlkUl3caFe26#VFB*HvpNcR#=uME|N6D@*i%j7Lj6Amu!F!??{` znrZS`o)^G})&_mC`@@KZvN?FM_l<8PN!uhpO5scr$M zfWc7{{sEF|4n>L$E^%AF^|)9`Dkw2w%D=TQ6Ngyb0j7l)`Le$N4CaQO=J<{_Ns_5h@O;j4vD!PjD)no;APj&IIxhqAQQLB%DzS-ckDxn%T^{ZpwnyiJ1O9~2lWJU&C@WRqa!x|vobg&gXNGWv4mq#T0vy+%)cKMcjw6Sa2D`GV@y%3~TYXcEC z`ACL^iEYdKZM}GzjJge~mkGp3h+gO7Y&sn-@vJh@_M^J>q;ij%<-TjH(~ZiI?gw(~ z;UP?+PN=cQ{Q4vt3kF={xvGao0BW)9;?YbA0V^+2TrRvG~RSVZcTmjVtG{<*MBMrab2FM)a-Fh5VAtXX(R1*o8eas}&+Fo|-`^xsDpWA)WvAwt zP4W<|j%VIYD~mqJoZE#XAHA&90h#9y8*SL);!1ACtV%42ue}E(;gEQRkt#^!Fi%*D%Sw zJow|lqxX+V5FW!&h`mQ2Y>Z%Y<@eep_EN^+#*dr>tzKsVpM>d?Qr23%Q7Po_mZQ%O zD92}r=iw~o$}-bJ{hRgpTz_;aF}G-Qv|M^;`9?upXtO7-w8qKAgG-vb&mIJc@(@<2Q6W|2?~6zQ!gw1^kmnx(B{$cU7<#7pt+g z3(m$BE^7t|Fd*-B6(fat7)-QzhORxlH7$0j1CB-ySv`Nld#pC8mFCsA;7N^Yv$)4& zz4($!wa{e4PL~7>SRrvW*pErb7>j`K*`X;K3gGj|5dg=!^AJqp|Dg6_KRsuWs_mw; z&TXZYIuyt7liDur7GGc}={j4Ydq&<>i@cQmJirq&D2la!SP`E)0;4w?(eB{t;QpnG z*)P~gsZ2t2DGR4y+2pY8^aELU1&qmGH@rd}T!t!W*R*|S0AYr2dK~M>`7*$8CGtL`mKH!JEPkVI26ct+3OyMmdh|@3C z4;RfyKjk^>e6>sfqSC$Y<}w9Ipq@|{7N_*A;VeBZmv4TO<6xWd*V z_v2Y$l+Yh&BX;IQ!~#k6+Ue@L$^(z*7BnA_osnwM&sj3n1bB459E~mSp0s>p6UCmfv^F6NK5rkm_Vs`NJ)_BKuHQt@kRePoum&uOP62!fuIW~Kg4qlfUVHY{c zbtwShmpL&%iCWpM5zMC8VGCJ*{e&CDzQ<5eSp(ro zZ;$B(tHNe<**(5;2R?p>QXev z=`%|fGiRxxqV0-izK%S(TT`{E#f8|C24m3PW9$p0m&KUCQZz(sfnvHC3)!!zKQUPm zDXJ_)FD*T0^_b@(M!Y06Q0g+Ur9QVigRc1Uf5`yoLM}YNPdMa%BK!L z4tea5zYjB$Q-9q=66P3fbA)%eWNbYBLG+15wI{hzKT=xv7`)`8yA~=!*9-#>!~&*Mdtd1qwJz; zgwVAaGnfAN$K)NAB+)zE7IPXXFi1;?kYUaGD4Hkz73f}yUb6zEXIR1|Z2Xrch9zOk z35qMhzXnv`O(PWVOUNU1ja~fP9|M}PF`|jE?_`!sx(~_LyikqOaA#DYr3Ge0#7$R(qd`(%hcD#NhoKLX50mAa0j(py{#vFJ7Nec&`pwa~Xx6#w2|ia~Ud zo?T-rU2=WSSfv8K1O!hP#oHiPyT?rvzBF5KX>4(+JoTVLz_!3sNT-P~aE6dd2IPj9y~dGt z`@wm6LlO61U!~)z2~D5RlxR-UxV%AlNJYggySe-jhQwWxp2f8FTYz0PqEArDC(VA9 zKi}YijJwi(_aKY@a{Pg)S8EsKn6NUp5Ua$n{N|V2qwIA?qGV7_5Q&((9 zR?z`E>qp)A?pkdY%|^vKriHU*nLpA~zS1ejdQA=+$o45)VMAxt#? zif{}~8^{qZ>-&j$gbKCuZbxR@b$jmm(C7r7CXcx?F9-EuY#Z^}uPPBwO0W zY_{xLsDM5nucR77n-PjhnKhUT8;dc&UTZN6)vbUvbU^q4PFQ+Ggaxs1;8iHwW7L}Y z7W9Dv-ZK?Cr`8WsjMl@d2|)myB-c$|-Ec?)*_o&8xoy-X;fTL`-1M6FX&VrSFOcFl zoKhUGW~a5k>rro+5SGEK(#c1|;E{H&s zkT&|xp2r`J=w%LbRVu<$lVpd*)d=NNA@ppqkwYo*o=P4^c4s)AvwUXoR8V+gRJUQ9 z@}@!4s1(X)hA!SB4rqT}BtRtK6hf!@TscORQK<0pVIoH&{uc2alZ0mZ6Z!c0z?Z`| zcRJwty?rgP zPH^8z(^$P}v${D} z!W>W-;WB@rOzC(zOQ>xqWe6-)b`G=$FW&3)(NGFXG!BAnmoyG>zS)h!*|y3`Ds~VX znfD9u7n$ZwD{;w1bTeOr_5VYtCWtN9sjj~+c($Tb6*B~|$S)L4c_LPGRUuGhKd-Z} zivqWQWNf1LdZ&(gZ|5&m79(IzMu8?tnk;@Y!^~zNO58fe9SFwG29~Hb3{ST5vKN2q zf>7bkVtDD&Yw>$Mus|%SGVVz@?>A-1fiRmLU5Q zN@M+qxldzhsB6({soXn~wjVTix*oqIQ^{viJ8bPZ99M$71c5tyxnoaTtTiDz$}gU7M?s9wA;#5{E2y}Tc*T#Oe!uvR^Z zxT1ZpORbGxI_X+F1VnI!+EB*xIL|I*4U~+npo2gS1sEr;PJhHh=}_SV+7}U#k5b)T zJopi{w1tOK`i~870LH`#ecWR`4`(rOp0~(V$aoJKPMrs|9;O2!+tQ)7|Cnr-gg+#o zrH27ERz55@yeHESbvG3!egW-m6F^HszjJRC$B}V(Yoz z%f;G#`vf-;nf#+`KgAnhpPlvWyZuxv-haBkbjiWH)u_;t;dPw%=uU(83IyzsoRVI| zd%isJ#xv>qGwHP!wdxUVfm{VGzRMDs-tUJ^dD;j6-V~z2m39WR=4Izn;fi;0nX}Bo zx;`B9>eM)tke+T}6F?9jL}c&;+`K&CE95thBvQlfj3kKTwRb0co`2Nkz0BBq7-oZ# zxG7DBx9VXfbG_ToG}b?1&6RWhqLxF4HB?lz4Z_egyO-iSB`Jp7NXLvA3G`US@l{ph z0n~(n$kuNV1F0NLbeaeaJ%NN7BAjxNvT^G=F`AY2E8lZNYp}4!(U*Xa>$wV-+an6E z!#_Ug2^1#4ao4Q&7?l1i9OMHUx@$IqB~^njMpDV%R<3Cl4HP+f7uf z<44rpnYFwF8MB~$2x!xJ8Q3Y=bd7>@HlDua8^c1kYh4n*Wu zZBeJxY}g}{d##1XxbKWW5bG|txt)LYS#FYrcHrUDg|UBmg8sQy6uP439US}(w)Yt{ zz0d#(zOSv9UX1(v$9s%+0|%M{0+QM*s<1xD#b`bf!6tSNyz|1bof3~zUmeNaql;+g zZoFV89#y@Z@uuVeq3D_*AHro~sfse5bzY9h0t*N5`{v``#_$xj+mM2qQ(8>9s7B%fSUac^RJS z{#2=o=n0`erO%E5;m8~wMWblMPtJuEp0;`wF!9X#A3Nmtv*znBsV)6brYhrWFz(7H zmB%-5eqTV<6MaI*S3P$yPW~Ev2x#5)PW$0jY`b$_d!ro!gAtVHG{dUl7z^96c%cD9i+3$%VS#aWj?;7M}R*DQNixB9XCo;3( z0cbb}3(S@3_*dH9-_}_#@tJ&igv&HQnaFA@Td0h`LbtE!91umjM@%T?h)6CDIZLM1 zE@gT1FxjrOGy@OkU4KiF>ziAhUGK4~`_!{M6-ofEJ(Dh0M6Amao`yd+Hwb7tzf1Da zh|xLk7wiyvgbOf5nhN|aY5wi>j6tGCm$OV#Iah4LF2?YW)j|yC_jpQsOF)eRQ@l~f zMc7)vjBy@OjnLH^qyQ?+41Ihd8)f(cqrb8P`KziqbKKJQ#-pz5o!4}Q`i#+F91&Gs zI&4#?q<{Rc+h}D$_M%QWFM!@llY~hVWdU4Q77?C1L{*SbcIB4}AZhV{IprC3R0D2t z*Cn^@a@M1lcATlODl-BrnzCLM`<^a5ljDjfC8yb3RU+oF(_KU$F-`2^6y8p^uWRP5n0BU+ zr-a>FYrbMSPUeLo9&wET@!>|*@#n5nNSslmFNKK0az{b2!1#}TD@~q?i_jq;A3~3Z z9WG1Yvp=VcTyiwXhqT<*)p3HhblQbRP*2ly<1t1Mhzmu>lig-A+ zhTXsO+u*yM`PkGo!TdPF6%5~AMmTP7XyB@S-;#E6>C?)+(4AH$r(nl=u#N0F@d<~~ z*Si;EKj(XkBdw?(Vop#G4P{5}_gZ^hWM|4eAzpIhs6~%j5NjRx&Xi}*_}Ibb|Ioa^ zFyYdnN&^k|8Q}L8vB1mCHJ$EO2T0H?{E6!Vk`m7OENY0vkyM27@oO#J=v?04WsnV? z{{;$wOe~Tsj87Zb9!F!syn7xusEjE11W*u+XY>k~RwdRFG64)7Z&aS=N__F}`CFkO z+UuTQ$p*Uq<{Gm2|CaarX1HH2>y}*YQ`xW6rh|1sD8ykOb20TdKj@8(mK`Pn;wEjoxLHo+k>Mnm?4?6BX0+89 zhysd1l`6Z1cx5t*Eh)My?2FDPzSBhmQDQuLy)A6v(p>-I3jC`+xD_Y?vNKU`h%v$c zMpg9xt>_2svtJ>~1peQW!HxaLBmZBf*8Rx{{QtR;|6k=s{$B;SzJ=lTbZ3Y_EF=H| z7f-g#0#!j@Hi;bOCx7CCl1#+$zpUlY=x&$0$ZbB)K?dy};%^KXIP~t|QY1&)BtK~X z6aL%D?c~dybOoCfmF%Y`#*bClZwgJa5AXJ^(-&lruQWOYyiv|Tl)y_k&a2M;2;5i< zN*OWb9IblANm6*>+Psh`EMeWwwk$*gdqme@7%cmxY`+Wa>ZJ0-`ke2tp zY-uPMWF^Qrf0?^7gt84piQ-uP1q1ts6CqFPnA)s!xooQ zDVwF*i$_x5Ah;-wr({Z$u{SBeu|w4#&&yAlF;~+AQQP>GL3GKb`*UbaI7_IPN1*V- zn==6KX#{s_$rO09&{9D3U{v|KK6g`y?98`!l#C)Ey-IC&;i&(N$2yH$it7V{ycV!Mu~*1-+mEY%Z$dW zLLrkz!Akj_3p}lf+c>!!k+lt->C1FcH8$>EgY8N|cp{UusQBOys$9?l-nqnhyWS<* z>~ey)(&id1^eVtbDg7gG)9a;4cf1*Mf1^jzY4=$B58~OiW^v%o!ahngNU2i{+P$PZ zUVTFFIBe;F4VEiEbm}UvgM2RrsozPTRXMudOy&X*X$hi@bXY#=IfO34O5NMVnjbcr5_g#Z#KaDk`mR=V{QUzHbYJ-yBAx z`w>bV$SzG_HatCcdfv5ZcDg6NU#fdU*nnC5ryIEgoBXfuK`bg}O3d7RX$(qx&#>2U zZ03W*Yka4@Q|~Z*YOee{w&e5F6V1iDGD%$9kTtz01&R>t#2*w2`&ZoE1jLg0ezrd<3aJAO*r-~ z;QZx>UruwS<<-Rwy$9o}Cx~rp9@sN|!|rSN9SBDI!}kM-2+OiL0H3th}U##=US%!nYr0Zpz#$i80~iK}7fP#jpDc-`4~7 z+nkRQ(tn|XHv4|h1!~$0Odc*knPfK8ycglS-)p{^u|Bk_d08MYBmN1DFkFqbVj z;gle0sdI3YUxr|_N_(rjlbw6J{RGD0Okt*gVT~iM_!9-$HSxNrWE4>iVk)zt!Qom1 zcD`cb*qC95NQSp$RQ3tK&ve_4`vlpyV%o#@*6GYWz?)KvSWvL|4$|qg`Gk*nbex{#D`l{{hPK*H zwSvdfsC1fEF@=r!71z8+_~5q6L{NHGC=-(gVkv$^)6_TxvA8WB()zA0+t&zfx03sv zt(2myqX^IGSTa)EKH&yr-YXPNV4DB-4+s`FAB5c+T&On7K`Fyi1u;_(?gpng zY%dsso!S)=Qy!dwB&#RK{DEKje7xPIZ==d+?8>dL-st=z6py! z3&7V8;457Q;ztG7G`<;WLAP36=Hug!kye+|G82|;9z`c@$}6JJ8-U>Pz*8Zt0NN(U z&-piu_*2xI&nmpHd8|G|){AwnnO;u)f#}s+l6|?vB{NpEOGJF%sG^BV*M97QG?x&= z7?lc52EMWr8w>UihN1p8tj2ogdkHXEAFD^!(kpNAMG%@P=q{WS%`pn$JH21UVcSh1 z$d9rIa?R$HY9AYNy=Tyq1C4E>k>~B9LO?K*tDAy*ZzyJPy{(QNwd0In$SFenPd+E6 zqDP*Ofwo!=DJr4p<}29)9db=!#%A4?2W{R*5;D{g@92WH;q{xZ^Jc7O>6nL2f?aHi zR0nP@Ry#k4TI`ncCTy%*zQ41YEib$waPMyw@q|y8#UoQV63<&O z?g;suIfw3V4=`P7kan51DELYAOfd*q5%)%2`0zFkwf{o`hqHk?Mp# zZnOWKD-B=>GSnEf3z>MiS$W`xQ%MJh0sxt8;K`I-(W>-4?tjOZ%sJZy190R6&N*t9 zULVo%gv_cJmQI^BX2KG3-1<`FYe8I%mdahk(uE*6)tXm(;jHoGt@`E+HUPNI(tYd< zV-K@#Ld3K@Ln_PoPZ7xh(20#mdkt!wQDW{*Sd%z|ZwDa8;U8r1izN(ki=$}IX~q_E zi4y$Ta+&Agi2BPpW;C-vMdpvXDojONAaYH^K1KCH^!{u%rP2E^5Tvu333IPN5~8bF ze#I?b3<5{k;P|k=!r}ugfzO-f^QBK!JlUUa?Ir`!KJC@MydcJi= zn6J_kB>^t(^@Rx2VKIajMtqZ5}G?B|M}uh}d*A5x4o@ z1uwrLEJ{#mlo^N;^1H@%k#yrvSID|wZYKmmxdInt!xI_eZ%Ha$t(PbYM@o2{{VSei zd{n>6`0A?Q!3#L;vy^E!tm3ftTl>WiV=uvvzw!D(qzxSMqhBTmlle5cIAy|=96A1;J4h`F55o)zc08@(U7o^KvM z1`>ef&>nYf)%GI@c?{N|GyDX#iy@5id`zHK7+lD^0TfI)V$Gn$#gfz&9Gx;CC zAt;4y4;~aG6$tB(K+U&-;O`5Y;vXE|Kg-Dbge^g^+$-p$SsdwQFePzM(4A6Mj`)G_ zIWUWbg2!XTt=axOhhcnr5VJ_`z;1{4^$&B;jou#wHiw}vqgT7(6c2<5rZEkHIMsD& z^-;w`Hhj075(KefmgnM#JB3b`DwCldV=`Si;kVzq={i!zA|}4T^=(lr3|<+Bm>TyX z?ByQkV6Hdco!nn{l0K+LyrZAf9AW4b+!+g{oz(Y0RY`ON0wPpQ7B7{J=8-;XM~b*w z&ZQx2KeIGHY!9mboSL4EiG(|xDP;hLGf11*fM89op}g@=aQgrWD*d!iPHx#!*eHZ# z(3d+2`Ca_}C6P&<&i!;Qa}9+s7lfP-eM(CIUGFYB zbmy#76?oBZZH1WVy-$-Of*>ZJLTfj3J-9HXQ_Bn7XtNoH3AVg48Blx&@wR8Bh3f~upd0V_z{x_Ain{N!&X{d{o zLEfJW#^bDYb}RDA5PalTuTa+<%PP{!7KkK1@{0Q?*dzI$1)9q5U_J>CiQ7p8RVMR> z-aYD|HE9-6Gk3^UE%(CpLhp6T?u%ftyicLlAm`H1&Z5*o{1o+csdj=l|MkmWuA zyX^Z5&Plp}S*4bzot?SaeM)3d?c$*qeEqQJ`J#$YtaCIa`JdIz&?U5U3{?>9`jd)w9a!meQg3GDSs zVHTrpkrw3_!y96b*%)%6HgdQ*ds)r0!hHI7HW&91#cRq zW@E?DtM}lSs-R{29_EXOLOZ&wohzEmrY3NfO=~r?$;}E6=5O!*;XeHR;Py4BH zf7X-NE%M1%vkJi|kAVD164g7&BOmc;y0{}p{%DSeK~O{h?K`DRe2ns8I+PaInR9>X zB(cLQ#WBPur{&`e5boC(miQy+$Q~F?4oh2KpK%I0JXC9X#5I)0i@7tD+%wjw9BalH z>HM7J|D8)74{)VWv4Xy)Ks;-k6#zv@Y0uwaM_Q>emd*h>?qNQjEC~s-DB%V3_$1AiRmKDRMgHYSAa;E;2z^$d)=LE*!#V_AFa z*_NV6<|VmK(p0=CYnF6KEMS`XD#j7u30g)#GU>73NI}*$jbZi7@j0P$ zl!oK$dvbi(>pf)Oox5y|9!G={a!K-I$A@*Ij_gEu%KGwHE%Fo$?}iRufpv#=zI$@2 z*49*WYVfUZA{L@UvVm#(Xa1;2Y6i?$_&a(_x%A*W=Uu1ahf1OfhOr|E-(w5IuOEcX zQKsfu(+N-aH`caiT|v_^*3{CHjIh?=fQHjpnh=7Z*)-fwy+I$kjG8?b2$eDko7^-y zf@ARQ@yx`bu&-Bx`J(pcxB_uz6n z-(BY>y2Y34^j@=3q=>`DO1B?c)Md+CSWZ#XvW3rhiB%3?Q-OQG;K_cPapLG+G8jNi zY-n=e=K-_H2?)9}hT^B*d2coEyyXFh`Fl_8X)H=cVC&`LUiBDhn%7g}{-}Qe=IU5V zo84TgfgtKZ>I!r&SMY?F<*)w*@ODNQ7c}cyy&tbUwpMnpO&RrLs2A5qMAdZ*VM112jJw)w0MIAgSriM@eOJ)m_x{Hpq;?tY{bNHZa z$EL)2dn_YBSnOrLY705Qf44G;4ao0BWBQnB)8y9-jOrXn`DHm}jweevT{9N-^Z2kG zwC1`%5nE=ky&4na5yS zsBxG*6t34w!3l@?hNJsx?Fad$Z>jv}BqRCsn#H2G;B0PDucie;E7q#d`N8CO|>aNZ@?Yh#w(OU#-McYy4D6K9UKlpsBrabupGU{Anl9=e`EU6NuN#x6$YtOm8$`N?m(h{=d`=bv5n_hA)Jkd|<|lT1Jdqc{w2XV-g3 z)_*IVPPB0I%z4&ICMRK0Bf4|ZW`#9^`d3~M7)yUYEB!V5M|on zxbJkt$k5}M5Oq(}+^>e`Q<4R6Tv&F?y~X9k0cQp^_JzupkfGdhUo85dW?f_f5Bw-va|b`YW70d%bMJ6_!(S%8ksz>X%t z1DsIAQB!r$%i#?D8?5)u^peL#7D)~7PG6X=UYMUuLgDr9l0v`DM|I`L00?VP-9d<& zT7`T_)87U%A94ew=3~HY2aiMFQOa!Jx;_c*=d83&`Q0Ck8OwgeK?|JXhtyh6mFDf} zFM4;>7u7 zF4r4`jT}Fobnvn6~JfWY3=jrXuo!VsTQmT8v zEE&|F zd0@A)SEuyXEHoi~C+28UM*>H!z`UfvK}}NMQD7X6(m1if(T~6EIvoV?`k^hKr?dck zz>C4xgoVXgBjWBLd;RX0Hm=JB(RBa08=X8{fK;vZ+D=2e`-Ee6SF5UPHOUtg{kKpl zKBlONTOahE9j>?ymRQ-9EI;y_p_G@sz9URK9p8d|NsQvfYmyd0Qi+_fz44G#JW@c6 ztsvhwZLCe+_aqppBCNX%EZl+m8EAnB@fXtT+1(jmvOm&tjwF8!WG3i1!mmG}MONz) zAcZF3G0ad#L21^>kvV~+0ZVvC3b4uh#q^FEF9v*m6KGwbjzgF>*4OT8wI(JkYqZ*5 z;UE4|G*c%V@Thm8TMm#2c+*$^5`?=9KFy~+fF?O?thZSuy}pb1qmZ-Z^F&iW*d6h~ zFzUWA)*5>UaF;BWvkrk>Pj=ItrKBJKz!8VAYl;P6d6^)t2h3ZH4lcIca%l@-PO)rM z1koJ~9+hd|2I;>-LpeO>DA6*3nRw$J+lO4Jm#*8#m!khs{MThtPCA`I$ynT13UKM9 z;C8-TdY)45(2(QbX40iPnHb{JXZ=F4vgO+d3Y?}6iN{Vf1<}gdAF7Y~5#CSGV&OUn zPuzCHA5zkYfhjPst`H{h4HK4e#>{#*TFwsEAV7l33a{>*QQ#PIZ@kC^TZ z<)`0hN5{Tr^-hfhvTmTM83_iSI-AW3c3kL8kpA!AA&wWb)|KsE@BN^-TX6QW;FYty z?&iO|{vBK^@Oz7|5{HXWTx8}q=k=vG-DV^%i=Tm+oQKSl+_cT)EOqsVPdMYdmkp(( z6PXqE$CPe}S~{CNe!^p z%{AW&#FGrb@>H zB3pNYCb`;-{5_X{a<7`uNdX?yYxpD( zu`~EHQxc`j-p+qHAQ})Sc zAbFDW{gIHXO3|97=`loO3^u66^mmGEno-2<#|bFk!iy%J>wddNdA0;^EK6|Fi z{C1n~<7sSJ34@*P<~K)=riWpAqEHa3zs=T@_f&uJJ(a^R?;ZMF#HKftgZ|vPcC*l$ zvLS}~5v4*~kV|L1+mD}qzxyBOO}5_vi2HNZ?l2t>thSTNJYNsaOF`RLL0tbIhwO*B zhy%3*wQU`&PE829QT}+_8p@DF<<;W$g#UN6=jmXCwE?vA`CTG8dCMT?Nsj$iC;>;b zaH5Zb2x?mfc3E0XL1&z}(f%S5rHmaT_e~(AN%8)!Xd|D^m_;^Q(i_`;w~K4Oz1ibt zPKsi_-H9Y$NOUB>AIbQxu`uJ4ezTwzL4o$tALK&U=!XL(VyCn3k#}OO{n8$AdoYDF z5D`<+q!_V6&F)3#v949&3uitgt=E5|y=}1}X0U@0l8oUr-WdM5I-I)KCU#H;TKVQ4 zZm=KUQU2p6#8EosWeK}fHXTI|*X+`CV%|%At;T9Fj(rapY)zl|ZF7Lkr+2O|?A7%a zed_hfJwZBRbq8uuoh@dleB!r+mq|}llO4tRBj^NO9hmXt)Oz;P9!!?P%H?vFsuYs% zjKpdkUESfO@;m&bl=)>Cp3?n_Abf%*KWd#$fcO5mo?<`Dcwi5eEKR*!hpN86+iO{? zM!zieyKXR;cE(~r(ITLe+CAyRno?7Zxp98i>@iQc`7T;IAQ1X-c!Q38^5hR2$G!x+ zzQ(`peob*^cH?s2=grji3PB$1VXbokSfm-($do2 z%EEKKCUZOA4isuz^}5I4>>vZUE=@kOC7L}-;dC`y{i!u}Q(v~+QW612o%uSKPEK}p zrgD&T93UQUa=V~p&}xfuKD~wSL;~g%4n!`PV}FK|L>}h*^V-K!v&jqzgl2rV5*T1W zP=C9(0?47!0@(3uKK7(l({#E=U-C^?#uEnc67~c!BIp~m0;XF-WPAL|(>t+6rk?iC zQz+=|pRCIBqP|7`CC;>{_JKZ&W0FT1OkxO22@OBKtypWd?}ok3sVP)0lqPd!RJidL z2|q;Ip9(c)9m0d>=m{36n^2ZU(rw^qjx~A>upuXaJ6VM^?b5uRyvEv5AE?{X zGU&AqjHZ3lT!%GTQr~%*kGzG}r2{K+sgbV{WxM|gMV13}wv`h1dn{YS3l+_Mdv_^G zQ#~r})Pe1cV1otkjt-$dz6(kUIoHu0wo2sj@kIif3Vcb29MI^x|1O?EePS>CTlz_bD7oyx zG>pIJ*QSnsIKDa%n+x>b*j9_^nPC6}+*wusUxu-DSr`=JTmQHBazDiU5^*<-+P4Qa zHJwDh-eFs!$(8nA8dE5gqy@PKfmyV0B}_$5eL;kE#jO1k6>{vbme+6>OPNMpcMaBs z$+vh;5j7IX`<5I$uZS?vYAwF_B3SYp=2Y9(UmMbBBe~JN)E1naTa9Tpmg|Xt++@aU%jX0uLCB;<5%YkST^y-w4 z^qU6??nzYy$gFVl`%A$9Mf&|63cjRI8m~1D-)x5w1MjkYJ7ES(tktQKv+t_jdn_K1 zXfT>0f}N}8&QduWIK>7RWA00kT-cOhAJosfL?G8u@8zHi`2`=K2CWN7m}tI4pGGS3 zIhedvTQ_2>a~+ucu&B$|tQ_`b_Ha?9DRHpYb>IV6?|oNq*q|0M8R1f$4aIdsxxS2- zI+UYt`!j}C@ZiHuAG@Y#z7DG=`V0z=cI{(7K|%L&#CuH|WZ z(JIztXo*ec4|P-Pl@`^RXt_fAMwaY{TGmjPehL=Ck-eV^iSBY{z~`2+R5KtY1jZ!8 zPo$%d;zD+)phHOvD(viCc0%sA)76~9+e{i&lZiALCmXjhy$D0mHYM#GJP+6JB zL{r)5$TI8nRB3m{;xa6Lm{G(D@;+9MLO;@swX(dOx#08*`-Naz7lt-{W)$Z!D6_OK zG&WjF;}ZG2uNhlMi1wxXCkgxzSzPtI!GpA0I3w2=?PMR$#22R^O@=E=RjmClGp!L0 zHRVpp&f|cmHCzr_XB|Y!{i;ts^_cmyUe+fl3x(z)ZJVsTeXX6PRJI=|KmU|`u3sP{ zqCYNBuSNhWghB|Oj3gYNBm4b*(u|_>%2;l6dWmPIs zbn)<~yE}C|C|KB^N0R5_RN@G3&!QZtkb7XWT{WP;IaXi_r>kzrx$B3cV8y*5;+&TX z`3FJ(6W#&1)Ap`Yn=e%!t~-Aa1XMMnObu1eK}0IwBYjjO{q-<4f#B=!bOm|rz_)}` zbRjzTI_{HABEF91i2BMCMwKzf6()b1jr1am=j9KXfGs+cz~!B71>=CF)SgXw%ua(p?*CC|oc5A6q##1CiAvD!D)Z~Tjniy^>b zTA0q4nGA^YXt#-JBxpfQBUTGmhFqj$uyd@VFy3Et$M*kCkNGdbVMjj*?cmK*rBQ#6 zV^h756k2PtX7y)zH-yQ6M?5Y>Pao*5^?#E({$rM5B7gZ1pq$$Ctym_GW2~#NN&8?j zm?6^zva9a6ffJAXm!ayv*?(xX@B!|=sA1ctqbcxJiU55gUp9Gm?XXBd+_K}^f#2Wy zK4la~ZHLul5W-T`Y5p$_?!WvwfP$ezf)qF}f1=R#T7#VkzD~C@N7~no?jRhk$(}fm z8)pgQ{I z%fBmP|M_LM0Htz`z%qWQ=fCBu|LBMHp$#HmAwDpx?`Z44RCxZoHU9eN|D5!%Zuozi z_wQcy|EeQpws!#9k9+{fGT=wZ)sX(8Uo=EuH(5k_$*B&H0Te45AwLKirELoO-$g}I z@ShO^_s2WrR?n+YL(wQXbn*aHD%=-cZP!mPpn8v+wM>%1@N zuVOk3EL1rfWGr1EpMb+=e5*fdf2G-lcmtc?(=`;2mGT84m?avr8O_P{ZVb(ACXdg0 zxkgjmdH(@VSm!$?$Fd8i>@Qx`FQ#i_9@OTO@5v}EQ&9iUcwUV5{aXB5_CXwW3(U`K4xYik3Qdd|k4L*6Yg^H zS_?+2*Uit#Tsg5vcgL?WL*G1LCGerfhy(^;j$T5Y&qmi9cksHeS0S$_7LT(PAV_XC zL*4mcwK6xAM9)(jh>HS@&p#5m5g&eN(52*8NWFX~NFwqD42iRM2gtrY^oC-Ey}&1# zbXzLIP{o(4OoqPf?xrJdpYBiPpQGnsuo#NE0ZKJMD0CP8iG$K)AZ39}3Kt)st&*7Q zk=o~cisO_tl%ltS=5(;_`S2ab&>f`xV))rt{73#dusUrl8LQY80G}eZvHtu&=s!g` zT=QbN*eP$9Q_Kp0-$lCioN%XTFP$(vy ztTZzr(d0&7Wvsb4pU|A#oUG+5=gTRlaHcDtGYL$$r!k%f?{LND%jJi}=KaT*ooZ6VT2%#hV^@mJM5dA+j`WGG89P+{b@xvs4Tu*yO-j zlZM~y&3zw&5&E#?Q_md+Yo6Zjnv@oHYL`PG@$jc<3;Hfa&rZPYJnpH{S;rnmq?P^z z!IQqR!TB-IHON&En6jXqGQ;n&o{eq|w|d@&>V~aVfH%4or*E#KnC;yk`(!7GZ4@uf zCa5)IM7zY5kW3YSG+TldCFX3FvzA;=LP?~uT+jG%tSK^0yox=^Cd>>Mg6X!}s4tpb zo(I;5=b^rdd>#{f?AjmGYjqa+voT~WNiFoyfO5v~{lKJiw~OD3F_tfxI;l?%vOnv` zSVnJU@Zq3|X2vax%_39Zk!%m5PrqEj7Iy#_5|NYt?-Emy zahaqP7Ke`!csyxoEvJ}@cxDS(c(vv;eoZ$=Ta&+6`?q%^JD%-!8SxngT{e3pRoS*Q zip8A{7IU-FYVDBP0UDFS(CqOxJ8lLtmqV^p0#2%U{?4&L6j4m7+MnwL9v8-gDcf&4 zcb{yhgAG&woP+`1Etf1-GgLSpM<*j5;C&}L0(H-?xcY3+oa{{tEj0t`E1eHl2eWy^ zD^0jWI1PmfbBy5ou+`Qk<%>Mo5}QgAKwC~0-tj zOuH5QI8d^YL<>UrEbZuRMM|0BsqNl&M-WMXyu!HBY}>w!cKcK& zBUz-C9h0j7)aTq6)ph}3Y_{ao?!*(Y1Y8mKKA0__Kfgp)Eu9k%R@)W7CY8w_(~$w_ zE@n8ej4yK1&+Mi0lHW5vGy`er{p~{5Nsp0DycZ_(FLqlRA+&uIr-?kx*F&Sw?SQh% z5Su|u1SqC|4ZfL@=g<5H26~*AcpsT8-cDQ*pDW$`M}^c@j*q90>!960dZ`S0FC&S4 zKz&uK(T0^lyXkY9(HvCG3cB`CxlEFeO9d5{UGikYWmq9JGG?B5Q2QL6#v0Hb++O9ut(tdE?*C2JW-NW zN4(KFDHjss?r0%ffVlGz`?d{MJfvY`k*!#sA{1K%kQ^J7 zuLE$%oHd{b4Rn$`2|R`ePTi51@o2?Ivf$-0Tsl!?(CN9*jG$-f}0 z6sV|%FE6?<7MR+JGTj9_71OBFjxADtYq-JVRgoV&A+#(OxIbde-s&6-eFD0CLdRO8 zB?r5el@`?(b>?H}R8HvxdS>y9iHS2O@uhd@!AFnnPsR@o7EuEe*TP~_pUU6$hD{tL zO>H->J{?S#Nov*ExO}c3N?^=Ns{VuPc?Frd?Yea+Q0Rm{nB;W#^8m!LW=D_^@svLp z6`IZNjV$D9Eym{A*~5g`bFJ>q`Pd^G>splt0+eWzaMRX{zN9s0Rn3kk@6UK{!w=Z2 z@rI-T>=O&EX}oPXVcqHMn$V9u12P4&j4G3n9FHN>rpt4dS)dzEGg_z>3eM0a%z0Yn z{{zQ#2G8-4h8L+;wZiT_(al7wjvDD%3T2{cFVeB<9mYaDn@RLggVnhh{| z6Hh!F=v7;o9~*W}^8o=}cB%)m;{Zk<22~XC5$U+cBXi0VU_O^A5syDL+aJ6ldG|5Z zcL)&vM6A7bT4M4QP=wnhxp~O)(;~ju9gx&n;@~(T;=iW4MIxappYR@sGTHfPwIEde zhbgwc$V)HMLbaHBOucMH6j3Z6T6-q8nthEoIn>}Cw^n;MO?#wAOfS(Y@ zEX2E_Wy7!!e_|qyl`p&Gyx3c+mywN3Wb@TMDDbiZvNe^DXfn9&DJbdF(|)`tut~75 zu>cvL>gR%>5R8jB*Q_rJm>)e}ap3b!d;!;>FZ5eYmMuGk{c?tM}M8H{a{O|G7e<0e4B%!;If_~H@ZV42xaP*(_uuk zR&Xw$q77sE`H8jxSExZAtF2rZ?e^=xD`{t5l(gZ3-9lDb*`3NBt*4K-I7Q0knc)OJ z7AoaZVhA{S5W`>eU_3^AFnap2Il>)ZB7$BDWnlS}ZSM6SZ<( z)9^MNI<<4*<4z}wscI!PS5M_y)iF;A>|vpO0A!3mHV~=S=|Cox*LN`PNo^J2 zy&)_Y8hOx6R@QE(G+vnBq@_)(yT-GD6^8L?z_b)c-B#lC~XLx02^1 zorJRZhpGJ}JG0w5oAi?dAiWWmz*}x~*zLV+F%4LtlTIl8;SFHH&_1$SrRqt(qpC|r zCVLGgd8s=RzUXh2fNZOAnvB7b<`XIU+u_u^qojwe9f2@Og-FbsQ8et$A22g4^d z9#lRE!8u{VRJ+ZV2n$ti3@Cj5G2!^oL*qsyLT^p0#z|s=Lyn@{<-_+i_=(p%8SDEi zukRJzfCN{kLG&*Pt_rn}&0suVv8RCmt@vKu@q;9S0St50gCT-vY92{|Ve7B>o!`uP zJkMvldNqRKkgV?4a_OCDK*>qe`{5sFyjGxa(5Dw>YCbjUPhBET*B*mFf45}b@JQ6_ zRDCOl;5*@kn7Hcp*CwzZu5U&&f@~|CXhD4H_y#|yFSfndJ z{cFGz5}9lZ2Bm-3aI(la+vD!CIv&0NJcEtivt4*(`|B^T=_z;QXENxzWC2WCr92BfS5 zK8fE#1QW3#8V*qt`YQ$&{Nkrh08J0o?t8RUb+@}IJ9L-_0B-r3C>}L2GGS!Z`g`ps zt>jd`H|{g!-VJ&^#Wi@IZ; zi92M19admLLRR92Qqo-@)Hl2ko`kFhDpxPp#b6~H#5een#|RdI7BGb-5#Z-?*yRu( zy}v$@luc%<>V8|PljkXx?I4C)ZTk#Wq*z>rbZYgBKdraM2W}e_go0bGGz{&t31PX^ zxkUv@y4n@1RY#JoPgK^}Tp`?Th8c@LJw4v&(JEGjPjuREESqojFHJ40mud)eIczge z_JyqaDwJ8_{o2}RgZmY7wer1hDZ z566yPuR0o=Nslgjh!kcFARX^;dDoMU$1_`MmvcX+w;sNV%^Ga7MPHe5(qut{5sjhd zDk5D6UT7EuzYE-ZC4&FK|6336I*3{X1zH4C17-jrCa^OAuA$)l>n2Nx&_mmEs(Q<& zvy4F`TKD5xZ%2v1D_}Jc|5?RK07MuFVuS>XCf$*nG&FlI{7aVYmTW&ZnaDN-)RS6%7f=lO2 zt%Fbmo;Oo2^&7UtIBj}J70;9MryJ22v()QKW7!J?S7wAP&Im2CVMxY0x9z&)NBtO| zbuCsL_s!`~)^RBX^7^8_lJz-S%|3~Q@K7oVu9v3!PfYr61Y8<_BU8!VFJ>zCdVE*% zTbFsPmI>ho*asjclUdTDp%$cz*4nhHsn8{~_g?gdI7`^1BwaOoxmiiz(G#Z1j|A!M zK^NpZdP9WZ{5wy}Y67_&~%z zU8(%!;klQ0*L0cZuJ6oRcZ#z4mM>`r0tvhy<|imOIl$t?_dkH1YN`C~BAO?UUTFJg)wLw&}~^w>MR%)*4(? zDOHM0;uO(tzD`Zy{LQ%1B|#k%sp=!c*fBmzbMw&&KAqn#hrHL!ANPp;FygeTjv=`~ z!;A3di*Mb$F4x-;MFz~1V9h#6hxpd$kZ>J+B9 zHu#{7(i2<)!b+`E*jz1j&{Axd3piO^R$@y2%w80_x}t+a<3|DMJh|COCl;l-ENYa#~t!xpIjcEUu~z7`_yqD~t zOdAuEhOEtOS#l9VSB=dA?^1|*ep|&kD(WkZ-2yfHtF}mM)!=y9_T%q@>Q_p+hwaXE z;v(8S!B}dxe3}`@yjLL~g5x)IJMwx^6S@N4%(Pv-sq79W3seM#7+MP%De)iUZcHs5OE2@4k_=*b7JTb>Je6wZxB*$rus6GH;Wp3y0VV%Fzk~ z0yf*1A7iOJOct|+`9+^sE^9sbc}2(Hj+K0r0;i@4NP#zWz)D2j$-+0j)|)AAHV+e@BCn@l?o;MO{rJS&Y2FgAym*LeDt4_!`0p5%o?GBKm1@Qk_%8 z6W+JG^;pISJN%(}0BYLqLr&Lgynr{t2$W-qyIJp0l`KuUZSYMTAHc)-AHT%lac3;~ zUH+uG4S!!BjLv9lQMq?6xU_#Qr*$y$O~KE@gp!yiyKFCRy<0lV0Ttv4b({hg#OsA2 zCIBygQ}h#%Zn{~bInE|-!Aq_^ENbCLL#PnVGnx7$3a$+X9k?~|U#{d_9TxE%ioQYK z&#<{mEa}L3gM^PH0(+@)e+Pu1=5g5RY#5)jXPXAYGb&l}9hZ)#FNchuu{oV7hl6>?jdy58;PsBcHi=8c0nqk3upA>)#gQzM+UOOF_pDy{}8db2D4c59hVM}S5;c_k{%;U9qBX^*u;V)kT6oY>+*ZOPnB;@ zMec8Yr+~`@z-B>luay1LCm;slaxm>KRsxlFu^XZL$9F%3(Ysuj8}_D+JFc*z`-YbYU^Jw*FNbZR9TK_^uvey7l|?FKMaFwl;w;|Yk+8=vFZ zq{~oNvx{uqUGl#P;vr=jp^;q7v^7B@f{A#QRLfO#oBUC$Twq-eqWYeAW-5vED=(@2 zafBw%Yd87uqU0sZZGsN1K`S?85OIca=Ni<*Hs|NSP)@ZBKFzJUrUFALaC;27`&X=+ zxr_k4f{e^tVfd#e6NeQ_4zsmS5<$DL;>wv^=nr|;*;pYLJ9m4=ua1)gaTLa&@K;}b z_li@Lmi8mDSdLEn!T4_P@ROxq)CJ6)_PsEe#m>JX5?RZ6Px`!J1ny-vLCh40*p1># zF9Bw6oO6kZ@wCJ{m=8JFuX9hwt_0ecbMx57Le->BJ%wwA7@lJqz4aa` z+M#v$rmS;Z6k#s~X2ll>?+3!ui_o&vMaW_V9ywuzc1`Uby zn@9O&d&i}aUzye!$CqyVb*9e-f80t7o6Yz_2`C`5@y4aba!UtZ5wNQuI;c+oh}|U?URJ( z$6U;U@s5dW4bjH}v|lO2c6dTDS+TC^5td24`>UgtO{0k0iAAwz^5U#f|6AW4zJ_1s0NRoH7$nHmzZhlXWQ!WkcR$y5zg#GV6kW# zgtyUQL6=!+lnAm8QWaP4G#|F8B$p4BGzwa%tLOO?LO7VJ9ecdEiOUPSF7)bR&O?y> z9DIJok)X1A3F?|4sMRaFJ66%BpuJ*9J{zkcZt=UEi8ru_cf7 zsd@T4PpV9=8VN$)%?$%J7XC*WP4$CX51vICVh)*@W~$1=FC7(g9q0$HC)!b=Zc`ZA z2ZY?7DveWHq|2AlTRP3V?Mmf8J+YUc+wv(oQQ1$DeAk;_F|N6KlzEXs*kCBieB|TF z$J~i&gYn=x`(38vk}g#1KY8HYGR?`nzVrM%1a+DU*H%_szsfv*^{<QfEd zbXtKx$QC&ti$e4)(7PTM$H{p!~hH2sE zn$NQiCX=H0vC+qij<%ILt{$JKTT^s0)yrD!j{f|}B|z2@>FFti&{8x5QaPp_6QE)? z&(L8u$I+^5qjSFuWk*%~D>y31!KBNjsg!;eE7AC6?BPX|uVnHwAn)LgHjatvhx&g6y3*X9jZQ$ zl;QVMgbZ{AmhG6r6}0uPGxQMDFN=|of~pYUa-zy^e7gFmkcXTkkd%lUmG{S)%k71x7;uxbKtI3a&P7SGh`2xl|1H z+)xnhhtxj4-@#|U;fdG=I^R7qp^wWNv5wrod+TT6mH3dZFvJ!9vZH{aN{tUoE^d1A|%t^4m zjkZiUJkH0eul?-#MbCMYlpjN;fSVJE8*mXF>HmswsB!D4_{2ntg8&@F` zaNL4N2Y9xZ<)w4H@~UJ3GSc*tU^~0CPr%}f*ee&Ov%hgwvso7jmkp-b;)?G>Rb{Be z*zLH;ho5(dEDqO%u`r-iTs7<32`o=9q~eXM!#ON@OlnfOftii%h-4La-neY5-l|?k z@{a)HWj3o8a&$4KieoYqhmMRtoj@~@rWA|7W6aG{BHoRFLz|nFmw_(TytW47 zc8>g5-fEpZ*Mn&9S7bc!E^?o8WU!?h=wADguhe=gv+}<0WcWp@vdCZW+?Mxz5BROBOOlIbm!(>tuZN~yCw>bV1J;|ri{L0#O4B0BTOhZ^c z1!YF;cyGI!Rdh{(O-E+#N0wGG!hYfz<8Jx!*m4!hMTRUQ+J7+Ih*71DC#2;l(bIA? zfBvj+tYXB(1mFI;RV@~go2hCkph=u;5YoSP^BF@q%FMr#PwJ>=$Ua} z5%^I9p8ueeXY+Ott4Hs&S^WUhlNN=9&!)_Kv9y8*-Sbkc7yQkw*WP5h2f=%nNWkqg zAz!rN^-7&;j@h-vYc>bgLobQ%*QLoJlo1EbGrhilO?3Gb`~*bashb&gfVql@6lF2# zK7Zn+6Ocw7ggDz`@9hfRNQ>dH6spv#Ap;d?Wd@}&jJUZ%P1(4Z^f_Bbou3QLks^}0K;0qNYx8vOg(A@cYz(+;#sx+mKQ^tDv=#Cp)$csn zhCe;QOSO98yodS;Gl6&i%(G;7`Z1C!;tJEygBiG z=NXV7crPxX24<>esRN3R5h9&X;e)a zg?@XnquQ&Wd`c#^>3x6f@_EICB*&;hg%j2wCvYH;LtNfbT3zB$zujmF+s5LW(|cPZ z6jz`Z&Z*mjr@Jv)E6p8N-w--L%>#SBwwuKa5s744BCrb&gVeXq;UJwDR*^8Cc_iZZ z6!*B4M!R`3d6$}Bl)FYRA*W}`x29&Q*HB?@e1o|N6cEz+A$6+exG9bfldio?8;p%K zIQ~T@-AXmtmhrZdduI&TS?;)_%;Bl`s-H1DQv_5RV5Pu?rf>VQku&LL$5I%xm-N#Q zw?~%ZrTC9A?zs`6tG*z)Ike!N8CpEqe);Z@Oe!R*f^@NIgEeoO_NC>5fYl_ zrBs6;cp)Txd|r9cx_e;Q`<bur>l)Rm~?8>xpEl|Q^T*u z3BO%`OEqT^kYP;ab)GLBR99lgNcUAeug;zV2yt!>hry`K(BXo=K!R9%9^&;0#pW|! z+*D54Zzil(S}GS%x-5gM#Um{(ARAAPXIWV;^^sC6$v^kK9x8Mx2eu=1JhZ(Njp8|; zx1xq-F4Tx65k5UtsUFJ6L#;3^&XrzenJv^rTkAS_+EUuCX2R`0d*6z2C~LWrPZxuh z#6Q}@*NPZq9Ba$1gWqbUeB#}BC&d9KLsEe_c(1QQ&M0je!CJg?H?F#7Pd(Opl>fc zjzn&9>lI4-i}skF@5N+sw@2fLdMyR#5U;THvHiiK56!oXVAA` z@-pHz|6r)PzI2Z!MBJNtV5^mj9S`0pdxvWkc*dfDSFd1LBt?Z(^b+1I%GJ9p`i%=| zfI0TbuJ%Bv_f8B=@_S>Xq8p|(NJzfIJ;lmiFgo(Kr7(I%sHhRr?+6;)cca@yc&{?= z&+B^mgxleIzioJLwDafJUxy$jgaZ4<7F#@eh#?*$oRsRg$K8i{6!Zs}o(Dcy$1-Sr zyyddT`Ej}93FM*Cs_rjl(>M7Gdxg5~F5S)uE)H#(Xv$$d`^-~X%C@WzfdCYBijlHA zTdHNB#a#Z*`*N0v0mxD>qMinkKpO~w?X-l>H_}LgH-_2(Qj{;7?A%8=7=cn)-}y6O zL(8{LzeCGL$Mu!@ z{tcsZOvOLWztR4RXM*9Q95Q0@)6gGz^*fX#>l9vM;C*oNFemPQ=`qkkaRO79Ck8tW z+FTy>8*F@)>8YleLORWinu1;exEGinL^AD9H6zpyocGwgzJ0t^)Dm73jwT+V5c)l6Lm?__8&H(4ekGvug>PEzn5`E5ziNGzk|25N4e4o2z5gh z7U>IT*)Y$&_%o`ZJ-lxAK{I@tVJ3f{<#!X@dl^}_-u_v7$xAL`em*o{LQd{7rbf?X zXv7^Pib~4YH@5q78^A*AWmJ#`Rnpr8COz!ucydOxQMm6yxG>NV|8?XmtS`L~_*5I^ zl;x$t|Lgt#e2f`-Bjgpv1GweXbLKxD)jI3su(t z^!3IpFp2A?l>~zcFLT>}|KMN$p{2nwccPBnkE)OSr^7#b(e(Y&du^!oQ2xUY4;OSL z?|>2DJ>}73|Br{UMZUp%V>f4da{X^7erbmP#|!s;hJE$w`5E)MPViqKkt3ZqEh@b@ z{N$5<0c=^+elb4WqWCcAKVnW!PQ16Xo11xZV1#6vgqoV)=LkKHaSUmHIbCfA2)#G! ztt$?Yvy;}Ti;<&~$1kH?7qdRX!QWqo;6EerfB8es25rmI83l;TBTWDEJKh5g??(O~ z8ujJc_`kiv@Fc+%J%RpDEdz7|nDpKM)ER(=T7M1PlN(>5ZN~rMyMfc9{11oz|9e^_ zCY=W7V{VF1XfKL-Lfv0I$Nzq3It14N;V~xDiN`~kA?Z7Qn>0Qjxto(m*;A@u9De-m zJS_CWQk$kuMgH)Yh9}l;wTvPW$m6|57|f8u8dOXeCAu<;I&nDyIFZS9Kg?l~;W9RR zL-B9jui!KASWT55AZO+yMJ9l!>jNm#_P@G>Ji6=I=9?E=!V6RDv@cZ5ww-8Tsw9Qc z4^-juOoq?q*>WTnd&gd|p5W%U0&+1yB4ADOel(&IbK51t$C?iU@VK}~=UgItpsyzn zVit1@-<|*-9<45?fWc9Bv1m?RpgK~cjzQEnc{I--bH(cAlaKc|`#?Sp8EcwcEL0RQ zL*>1fj8VAUn-Iwz=Ft4@AB4JpahYU-LF=O)4Gmm~vqRm}1L91Hj{F}2E<@^gIxX?l z*UxO}rfh?UGuZ?)#ac_Qe+d45+CYhSD8t;niOd7ab%Wcv(%898>}?lpbopWHa1WJ6 zsBa=uNIhPxsI;2Hl`QBfHVg4*FT_CY{3Jz{T>IPti{7d}*0mNcn7mnV! z2b#-SEnB>?jeSb;T6W%zK5MsDI8j$WRC~7vh+XBujeH{M&GnM4@-H@Xa^3+{X+PBU z_F|>fI_ovEetlq@&NwoWJ7zpo#<;&nV}}d6>wE91;#nuN(98rDL(zOUWr?7r1g|xs z5RNHU9(&@H*#^37x8@Va{Xurk<&M?Vy=t~Xe`U6OWDv|X+(^W?&TQlTbnlo@=+{l$ zGc}Gg+@IUrTagYYD&ukMVCo3?KvwxA|KAvx> zH5j+ykk9N5mW5f4c98ctDhsEQPoD&cg!}%O1AG44l6(v?Q>|}dMpK#v=gQV{jx;|^ z@SJkq+y4T#LMhd)m#oV&-JOl2m`Yv|wV#`vdxfRm2?rA*7(fNNzd1#%Uh()&?0m** z>ReD?ToLSy3}j9x!0Xo^{toQ{0x+MQj%0V4bnPuP02x%Fax$fy!NYnoTmxt~?V6Kq z5M0A3cjJx*XAARub@|KE0H#HPvY z8T6V}dqSN}68FRi1g>xgin@Ok=aQ;-_o9QPfAZqxeoE8k#Bg7#vPvZFAcTTWf91TN z2A?kqdHx9|Lt_<3%BJV>d)}Kc;`X?vUwJxYlA?V;&XKwG)!_2#|DC@!zpxI9C{o{3 zR4Peixw2xsQ4eSjC{{0%f|W9#^prZ=`kvaaa(N1ji0H)E9=`z(!X99_xJ(Dn{QyZg z8}X`$u;{9nX=e^)MKA@Z&%Kw7RS3nTp{^R4Z$UG<*xs4%K}+oRdA)rHkgTtlx%NqE z#m+I~IPJ()#eS%VUwse5F~P%?*f#0^F4WZr4~%?LXwbKn=DyPA-^AMkN%3Xc%|c(z z-cWg588Pp(-cD+NXRZRcIxDEG@r(lL+RjFA8TarRRKM$@5Rw zc5^6XBtSF_zjyEEv7ID%EqiFQ{-V-RDna{F@^Lh2NMRJ+fWNfeKXEE^1acfscx#<+ zR}0#8Ia!Uhc4Z`Frj&~tKTceX_x;#$e_L%jc2=}m=Y`uoq+UK1K~PT}+ClDAgxcGj zutn6hoRE=r{I&IQj)>dtfWFt813UNrNIAi9-Rk^&#?nx3C96PjHTqF5WzQ?Ykyfyc z3PGEU<-2)Ul|jFD+t>XMtqiNOF`;9^!YoXDSpBB^Z=s%VHsRV;x@ZiRZ#G?jrJk%j zC(EUCNdR64^u<(~p8JjB8zl!aKpY<@^r6jbNIZ_qh}(^X@3YF% z7!H3JCkxaCcG~U44X%x+1)tYlICDF>nETKtSed9)PFV<22mLEt!IDKci<_Qp{4{KD@g+lzT6b z1M>_C!BDAz%-K^q?N9p-dTIT9i$W|sq+a6qONYeshhLM+v5X!T(;v*ppb;(aEbc4( zK`{K5_|zQ5|Ha;0KIPH0-QKtc4=yJbJXmmdcXxujTW~mm;1VRbyL*BY+=4@JcXti) zPI6uM^X{kW-v7b=NI}g|Jv}|$$FY8E$u<=T3oYGdS{~Hxu%6CbP+ZDlqnU%(DrKn& z*v*OLGdVFrLQrTc)2vB25?bB~i6r9PJnCk|d%C{-j!G zP?v%eC>0ofFE^|HR=RV87y$2kzc5`*t_wexnw5WRaGA4LXFpkKlqWj5I~+i@BjPi~ zBjyKu%7O?RYQ?XL)PQi2Vn@E8nco9i+|OuxG#T{8zJyq7SFb`gc&zyqHvk43MF#vW zQx;a!JRwJDTG#j-K5s`e_$AT4-j%BQi`eOYZy@$gena$kgDM0g^6?gI_BqpTT%nRu z`Lig!)RjbIsZ=1-^6hSg)|(VAJ4T=$xJXNkgCOv-FKRtq(NQ^f-3j8$8Tzm*i*}@t z+6OHq-%gN3tNT@R%yiUhJ(ajM>Qn9ufdB;J_<=TXvHuXHfxpHZys*bs|$i!om9=S4gr%QjKjGykq`#H<=8Cd z%x_PZp>hIdaychoERw7_zL*jg*gT!TNL&B{RL{j3HuKFl&F4mxn4DTqmU`7KYJ0zW zoE#WMJ!UvR?rSYJ4_qwq`9Ilxwp{TooX7;-7YN3whnI?NT-MrWf zaK3b+(N~7l4`FAuUe04FTlc9L6Mx>435aIR)>b5<%aeqL`jA~W7s*6!&fOnu+^^hl^)tvz6;4(aQqT$whTIrTG@H@oF#|6Nau z+lkmS{2TX(IZ#bNozaOk%bs5BZs0D$;T$gMb=%?U%6&PT&);JqX&#wUzOW6{WIu*E zvPDcnt0_C-x34Qw&ljO=1%@6=j2nfm>H`Lh!BXQ8-OH7}$lbDajPtX0YwSYDU-c)0 z^iRvxXhheNUi;tqERD6ZeG1;PJc9NjCO2wTtYFi zgJQ1VV zbs#5D%jSXahY^}c*Y9A~B9_{`ogejb>YXJ`7CB2$`1up$7 zt#=a<>g5OLMgxpeMN#-x2b&N5o5+_!C>5R~91ZR(UfM8=+PTZ$6F*p>mKMDpIdn?P zL#hifLuoMlJx1RH;}esWWA6)XrdgvJOY9x?G6ew6=(3G&$H5#9bE1r>q-bh`$bPAs z$IL}}J^op7Oou$4+4OCf^y)^(UvCMRGf+tcA}X9MDwJOM64e=?^f0VnIvv*DBP`HI zSCNR+LpaH}UVg4&y4p=_4OR}(x$A%qeWx8<_8z9J&v4tX(d7Zyft9W1Bt`Xr(6M_X zsU`zO{ild(2_s?A!m7H~?F6*r(gk-rNNL@N0mm`=3?e@Xi!uEIL%%fzU_YEt__51$ zW?eu*jDiiZqeASd>k|(8B2;!b@m9WwzE`=HkW$vUuzQVAPIO;GcBjqe;u(5IxwvYHuVB|BG-#%}D^^@V!#yhPe~+4xW|QZ70LtIB|26HY>8 zmAl}Dpz=$3BAXOK2&-f zpPr~(@*f%x(g_d(0|CbPyHM^0_&VIcDKvLyt(p_ZyiEP^7qeBs5O^_qV6Q>4Gq;Rh zWrR%g+iSST=-tkq)zx?>;UWacKQX?Bs~~-Ce~q062G2j06Sok{l&GkjbVsYE&W8R{ z$gNZSH;DxQk4m8qJ(a$f^Xvi3=l;rmix#OapP4qJ_og3UblzxH1aH5R<0j!Es}??fjD-zDtt3kKlQ8cvf?DAxPLch$fP)DUbnr zdrWFG6J>ZDrp)RU=hnhdZm(20@o{&+Ul&!VMt_j!@Cz&T_}t=Y%Qv-V=RQPg4l^I88Q;CVtUlj@0#^+xwWqC3 za=wcg=DLxj=ChoRU%*0f`yH+8_oZx63V9U55@8<-dxfzbZYc9d)PW#HiN0vtXmPTg zn*&vzl5{qgb57(5H;z~h1SBLR5qC+lvYsWDk0nzS-prB}-$nY0#4&TZIAMvthuzxp z-?2BCTyj6KNt|pKmBnTMG;i#2%C1z@r59+3Q2*T{AL67xihkyaaHcA+&~Z6WaQ;D^ zwZM=Qm)-06jeDGAoZ35cFj@lUwJ0w71XX1#0VP%uUSpXCbNs&bp%#(Q!zXwRFf5DD z>P#MP*kui5j0bK3KA$m^-BPn8)tuk}ogfbL$6>HI)3eKd3>$@mlEN)Bb=i!S{c)P+ z)2&UnNC2Z2gUw7?saHgDB!<7xAN{AGCHKNZcU;Ihs8iyH17gR*slh0e zKD%K^2uIhe*-`9Q$)SyRxmw}XD0!Zz*G^kKgo`k1_V9Ry@m$jerQt_($E)a1fY<)u zWq?@+)8()@ayb)a0U@__RB)Sh(y&@lLr;Nlbl`W*%=NRym)1h-CxB==SVBT|2}ar( znLN+^MR$9i&TNz?dUI4fN_eoP1{yLyfB{8v0!Da_EDu86fS^U?%O)zX+`V=RpHs$U zC6IlBm*U@Pjc=5^CX>eDAa3)EXXf;08jQvPchukV|9K3AfqVIk6_w%R=;5$qow|0f zaE&alNjG!x=?^RGMlV!MXefTSWAx$`y(GNb6JL1Yo0||Vp%*|#M2>3R^~or7p12cM z1q+=^hfA-OHq6{qn9F;3&HVT==vXM{TlA9#tNWDPqtlTCtF_ukhvxK%P*_>b$vm^Q zk@LefUwXTn9Z_`uwozQMT2rdL{Lt%0J{eT8e1j`gIAt9c{|I4Y;7be7o&~5p>oPv9 zqWffWB{OO#9{zG{!%ATZvU>uFltWJyc|vb2kri@>kJQJ|hi&=PGJkwmLuh#M42N&l zs1=OxQ~a7R=1S=DBYl+^^}SX`Dy92i`i)9v-R61LjQ0h@tQi`o;H@G79)i4jsIYYblpDBEWL7S zb>xsYgOD*9CX`(uWINB0_e&4Lr=S2s%%}A@?*M`{7*N^6QHRGdj1~%BMq`&1;r3i* zItu&q1l9fCct*|BrD%IXksSN+pqoDgFuzz#2BzbTD3lm((Dp^y%QTbjs*hsH?@I+9 z!@k)sSZbf#BPLiXtt;VMGdNyzWy+rw95`5X_Mc&sXP3>r!?t_uYjubrN- z;x`bf_-;D3g=Tu*GuL2QeiE97o~p1iIh^UL%{NKT>iOG7LO4TsdIG*qEcRu#McB=U z)PpytzVK#ko|-PzNo zy%U$CC5o+)hQA2e@7>5zqCG7weVcz)!yCHA?XhVm;9*8SM*P6CM+}ramGIZ-g9cDh zm}jm<6V{Kmr}YH0YPVuCJqEk+x84++0kh;dgFZT79bHx16W!lc&j<{uib6jb8%9Kd zqv;40oU~pK9)DvxoTBN6>Iyb&5;8Es7%7V?yU`hhCGhuVz0w-HU;zo|bUB>ZO2ZROe(%L6r~fhCfm(}MEfT8*1edwKlnR|I z*ZNJQ9Cq9`_f|0DERWDBFtowRPzUf?s>euN0XJv?C3F&WWX%QYJVgk*+^zI5aO=0s4oY!xY%8xw?BYzt@lR(1HbX> z%?X`I*EI5ezCoWWa1!kfM?lY9k0lZi%ZewZV0dA(U(EPb>CuKR(nlf*V+^oEV|=Xa zd?!p9cV+K)$v+MlUZ&Tv;+ex}#eMwSPa`F#$SEK1xlkKMi|2K-XjOV3=JovgX8Pm` z14u;ZByiG*gjRq}C8?eFY_Oy;Ri>554X9<$+=-(C@K>~jrlsaqCA=r{D+)@5TK zbEVHa84aXL>Xsak_R}y@I1fv>9S}O4&%<7fHJ9xfPR<>1tG0B$sX3MDSLT^Hx1*dM z&h23x*Ca`LWv=(YKomi^=lSpEU_+naS*Aj$BDg2jwgPv+2{*vCJpmwHn-qwWr<%L* zM6%5DJyIXq0_jR{bU82u5M7}h3ax^Oop#<U`pq`Jx$dLQTtCvG^)gsq zYIM8$wbo~a_aSOKD=Jqz5`us{GP#;%_y-COXxc9=&Drb~g#C7m$)qph;O#hCQ~)-V zfb@BiUHgJI2?PRTvt-r;<-G>Seu-$f!nQY%<_5^rz9@S&b$C}?mX=c+T(6Yo<`b=K z=$)jompFhzm>O=;CyJre6lsQZH=SMuI`l@20r(AYWn z_NsBAVbt=Te=}E{+2j;+k@EP$ZL{byuX8r|u|s7`ePeZ3^`Wwg2Loj|oapJ@4+p4% zsqa9^|J^{h3vim^Wp|r(&z$Ji#?SaF+(vf4Q_h)q`0_(n(_7OmWV>wZFlN1+1E!d9 ze!M4!ByY>e9nOkT6+~SE7X2-LCm9%m%}_HReoEMLEC@vjqCy)c%V~a-f+iFRsRJc* zEL9(Y7O{Sm-RIQvh*<0%uC9?x-*()yBp`N&g{}gw-r7?sm4_JoUDuqDY3u}sQW#HZj2)5jU< z?vd6lIC&xlgqj%rVo{R9b#_dJ1w3gifY9MqWG(=79M4~0~>l{gy1#m96|YX z0oAXC<-c3i2qYkZC+G{`>D$UN)zhKx0&EW?fZ}E@kavc^w{QfrmKh;9T(MSzXt#Ww z&+xL|vC=(cLNq1qtyGM#p3>b>&cyvY`?3QOF;6-0@ z`O@N|xrj-T`Ox$&=aSrDtt-@p-(wG4z7`XFHsIlu@wslj#vz-F+~(}1r#o;3qeqr+ zla!q9SRRmh0Oba!F78KhmvdmbrxG3uC>$EK*67aCTZ zetqgaX3ZbkckWPok$C=L3NzQNjt1UC#<0=kuCvbi{JMfMc3s!Cj4`m$P;E_#W^NqHpM-yY6}k^t4HB_pM z&AMxC(RsnKTq}W~EVn=kZmz_j{jqysYGYB!=LQBrnQ6C3fZu#V@#iDEHyM!6`H>s5 za*xpiRocdt#mEKhG2ps00HV@8{)2~rr%r&Mh_IvWc`}2(!U>OSeBXE5)x;XouK&EO z5OkMHNulc*_r2kmI_|KNW2O3>zPNhdCE*wF$;U!()r1m<)m<3ODaJ276Ho@eWhFw6 zLRz8=fy?>P)gEv|B;AN;i`<1r$KX#cz0=Wl4#Z}Lb8`*$0&-2P_OhN?rKqKO4*IR& zxKPI!TDHVIU<6ApY~S-8WMd7K2(K&aaVK7VUOzHB=$&46cgS)B3er9+XaQ9`r&gIj zsg6{wS(mfYB3*qUcZ-Jio+)7c&Fv~Qu|NX&(qhu%oJM#NZOBJ|kv*Y^^0I3blql%- z3(Dkz&M=E;rYdCX3Kw`q{c(bvwX{pku|#zx41XfhcFkS5SOlHHkuR0}-lUmeC*&Bx z;#VX0@Pkq#=y+s;5|BJUA==kQsZAR2DR%|lQ{|U_6dM}5G&V+cz|Sof=`AB;Y+_J- zBSq*fK7Es9EOGwkyN)lOuD>1Hk*GYT ztFXB@Y{8G|(Q4=ck!Q3)Tm8`%L@p&*J}IQOT6gVSCJVC?hwHhbmnD2}mFmQt(zyI8 zl*Iw3wu8AfzvS`9yofJC%RjvB+u@=S7m4DU>n$ddA3Eaho$_Sb-Lt(RX6}o|M2-(%Oyng1k{H_`GKoooVFDSuJ1xczJB~ zAw~-UBDdZB`7>YITZQ>^gY{BX)`pAQ6W+S{yUkt>_fV&-XTiMV!7pn?DJ;e*g=|V}*MLuqvno(h5=Mh1I!mc)Xs$t95F_OUM5lL#!>2T+v5x&)rZPW8lGXmONMH z7;Dnxo$=!S)12(DB#u0o_w8qFz;r+mz)=;7BjfTXa_N#u#C`Vf^EsaSmRd={$6p;& z&I_(C&uw*i8+!4o`xFX6xDErH_MW(eg1W5@o;{B#1AmGL$X5mb?`OZ+fBwLXp#F5=zyfby!OBK&Z6x?3i_E51Ibwn6#Nks&~ zW3;)rXuvn+IA`xUFJM_)61&AjSp8jpv1l=4FtO`k?}Z7Bw8wlqaL9Bj>|m7M>W^}L zbnc*UE5cz?9xmyr7_G5=K(d}5+c-v4c;TqfKXER|u0jc9x+1VJ6s;K3A3zoy zmgIj#?fCv+DsKO>FZOmw2$MRRVBb}Z;Bop-RvQi56=SPH!%rs~EMM!{(&R}84;at^ z(0N5*dT!}qWk_Me8X%+_MdL~hs^@+OoFg(n=4AjCHgvwpqr5v582i>q;7q4Wt^Lm-!#v+C5$jyzf6{-kht}7C)Hc#=$;62IeB*jXgFwS2#?N1`CV4 zP0bY+^w7mv>;3Ya=GG4t@Zj@l8!h~k2(SugOU6!Wz9S~U`~)XGp3GEDR4kj!IQdiC z1*H;>VFC~oPoCS_Qr!=t={{C>&`E#_q=eyvaiC+qw>oy^%7k%!x{d{G7~^HWFv;t; zOaeZa+aJ{KN4~z72eNTn1pFhm2@^tC4T!f_ULk%Nl_Y*yiPA5Q>c{sz7hlQqNPpJ%Dbzon z&KX`$G7_C6arf-O_zPR~T{tLNXoxBJZ`^TYPfDc|5*sGLAS0jeqK)qXYf_RqCaDQ} zgXeP1OLxVlZ&HS9e5lYddj1K`-cz4!x9GhNweE2ag23>PJd~Mcry`z31XYxsfHyla zpviG7jBY|eT<=O~&DVn_Z0{6>pOJd9;2}kX+$72PjkHQ8gcWAbIVysARCwlTH`~ zwD4y#(m2CyxLewT;|JL()#q6oPE(>95HEw!;@Jqq1w7r07DC|1wgEW0)Jn(_4BI7Y2m!hSfjPT(K#+2Rag!j3=5`Ns2!L;J?~71};s@7G3& zra!vc9W~rz5Ws-^;L^Sy8Tr_=bYAWLq-t;%%mwrjBU>nF;ae<8QAo$~w1#P$n^%GP zm`Urs(Z|{!hVJR-s-}Sg6ydjfyt&%$R8nM$kD-4~->&l!+uoaM<8L&8^F~I(OHV%t zMZ~5Gd7P^owP1x6>%9&m> z*em#H_Qh{Rz?t$lP!8&NgS=89r|pCXHS-gd4ml%EZo90GwkZqg{8PvC?b&ds-e%5b zW1$u^{I)`9)F%)Oasg^Iqfq5O2t5j)Vq+?Uf33B$hJSxJ9@gC8!RR}^8C!wR7;KU$ zuF~NB88)5SSlaN2y?r=;p&DNTEP|vxM}G6h=sG}6)g;hpJ|OEl_drYh&q&O8L|oXu zYj!bB;vGC|yR!bWN|%W=?SvAsPJzZZQT=RGKWy*Wck1;~h?5%IF>~GI0y20)Atw;x z3ZCI}DlD?fd!B1%_ny&*hR@H3 zMft^+2E<5XW(%b{$$MA?^cTow#2P|6fyn(J#$4fZWs(MGorW_bUI+EebHRWRdVRpH zK=O#=r2xKp*~F+&Gx?Wu*T}ngXuQB=gnPCwNzf?e8dkB_?gB;{OsC7hty;75I|6p= zis^-;k`KO*uEC!?+-JGqeb3{JuCJIndDyW`+ia3!*lh2mM5CXs!v#^W?c1&ka&)G%Wflf~l@=RH-$P~yqQ#xf;9J9hoC*gM65 zLAP;i!a(VZQf3v9H8N{EuY*Z?r9hd1gi6GrK;@v)`FX-pN)oGRl*K(notxB3sY~It zxm=LK1)nrQ1`Y+pAtddNVc6&E8-G|i8 zdKeU(dQ(gtDy?Hyriv2R^DIH9N4*15%xrr798cA9Yw?Xlrg{>FC*8Ko(vKcqP()IU zy6py*j>pUb`5K?V+v8cCa@JA%KrK>>*^s16y%L>$zBt39TP9!a#m)J*BZ;kIV`R*U0X^0P?X@j`-6 zu1tc*G<%nYBO>pQAm2&F1+3zt>N;M_XdxZ8kgGkvJf9g(-l8$!LU-5sDGqn|zjibv z4~&idhQj@5LTwJtRxglef8+B+W3*>@x3F%- zoe}d_XX!7xelTs}y0!cMV(C$9(NXtAWzh)g#vo(MSCgx~ubUA8aTSseJI=K&C8(ee zE3%8UCktuO_u-IT8#H#oamKuuv<1u{Pjj$gYntd0>KR=!qhX;WxJj58^n9r%bk1nd zK`u{Tqg!1b+}wDIk77x!334pmur=wc}y26;AsVs{5J<3LgWbz+Ja& z;#g1btzEkZ0R)SBR0tr-BIguw4U=;B8Um`rB+Mev zki1|Jftd?Fp!6l~Li?T84EKlYaQLH>?eu^3%PqT7OxcXU?);gWIU5dECBu4J&k>(G zSWkwHfMH6YbxT$tMi@ZsKs5rt@+j@{{Hsvz#sug9qj3BA1tI#K=d@C*Q4B<97g|z#V!G6`@yQR_kw$+sS02Ugf4le@u`5su+TGVb&qr zML7p1gRncEb+W#Y*b|PRxCldxL0XLJ()IjywV{vYaFy}I zECd-U!JmOLQAXNE#zS1(?+Huf(%`N0Wo?fp7|-|f1?2Y->v`m zVE+B-{|)$mnMwbdW<5r)fYrO?7jp4Dz;K3hxq*@I8|`vH56Ztx)t}`i{73!X_`zy> z=fN|~#S_67LQV(ewx%=X?ZGcSD5x8?^5%}8UQ#eN_G*LxI!7yK*ldx!=J7ZCOq&@Y zzM$_RC7nuBoZaFaHU`Pkb<5+}&ELm@zKZ@|KnU74_Ag6Y#RwU^{$Cc|kaGafkmLy5 zF-7^qxpP^{&(Wy>j8>bF*LH;(jdUyu__6wIaRm=>qzZ35m`DU?_aH%LlivT~H2xR% zP*KxE^e3Spo)S?!mV{qzcv~clPOVG?hzk9x6tEn$-D!mwYv(nc;`sb`tlDV*hiEti ze3oV0Y<%vD(zf2L+Q$KZwkOE08shhnzm55yGb_DBp) zdEFf=0CDI@h4Jl7R3#ih-l#N#%yB&SQ=3^r2G;6aKli^2>M{C1JcFH)*gzOY=B_;P zi3jB{M1Vs#Hk9`*DDMxhkS_2{i46Sk@YCQNZKKQzs1dzz>`MKnnp7|X}zFqg@w z-%REGp~i#ykwN~DbM=FV9go-3TUZ#L{z|W0|~0JONHy{e^0!Dj`h(87epSe<%l})9J?dX2a*rs(6CTP!33eANCk08jm<>y}!y`kMWM( z?!D)|E&zzLHe& z$dS`OgQpiQN(ccx>>S4b_2t>_^g&E z9x36p*^7~-d^VXtt(XxJ2h1qOf2nUPWaC>xY5?!iNxQk#-DBAcI(x{rF$XpgBH++!IMAgX@bv z2IGdu>2l#nGShVB(sx>ewx&OjQ3Qx7OO{6w`gk$yNwF{D++`#4_Wi~yX^~}T{GUBy zpKP+&iZrcw#9Kz*eXmrW4xpzQA|1-Q7xpfLQ~3Ic#~zK@-1r!1yes(k<+$gDn#ZZR=q#@s2ERW80;dmlD?9%rAzUlVT%>R1COba;%16`eb71td zz--SBh<_(<`(&;)TO68uI&CpNZE}GM_S%YE(^KpPScbZt)?tbPUa^J}=4Uj(B$5~cF6Kzp96hQFUC~|KLyl0q}jaG8kAt7?rqyzc5`%DdMQ zUlu-GbWkm$?Vs7WyCAq+&xfmC)pBZ;*4wgw?yBsu ze$ko*Eptbo5wY(+`t(>pWv_j5+6wcdEVP{D`%TjB@#?_iyPNTY3(1$JBbG0dP{Vhl zW-b=kdIWMy8RdypOPv8OEtyh&Qiv}XSkxt!7u7ra?f~(?YAU7J{s=PxsAo;J%3=lb zDs?#s@?)8*fh6;(`|yN=Z=}CkK9c3g?x7}9doc2At&ac87o7dW7aVH+-}nN{tGHYW z%dH~N%|}ne8X>>K`HuI6vKE!Bh}~@-X96-;R@TjydAKz|T(|XXQRbU8UT-+x)H7s? z7ON|b2J;i12OmJeMId1Oc2u`!5H=`4puNPak*;5MdeQBuRJ#5vtKret(Neo~4T;p< zbf!z(JY?fZ80YIj$cG-&St@YA&^KY&3+yv%NNCUvc zBqLlO&&Xh!aTS8u9MsYy-hR6ACWDsuAB*S<04#;3E|NJW=m?NuK>pWcjaC|Kzn1RS z&`2h>@vjOk9Fr9Fg>jB%OXL>nnizY_+0JPMe8GEH;lyUU>LTZGwrS{?4-#bM22Pd~!xie<$mM@Xz(;FuW->k2+8QDqC z-_+fBH{fr%Z_eNYPpcSU46L<^(~d)B3lJb`iRm5JzPZF|#W}f8yop}{(kvd&gSHj4 zg;&!XoaR@8XgDejRT(H(iWj4ocnUuVULQ>cPWfl`)7~eqc}!G1$m;hKhdY~&%0(Zk zCl5?;n_;5y_(fVM=0J)LVD5tBzses@b#C(89&c;2B4iW#_#GE0B)Ipt_2mUqBh#0- z?2!z#?mQisM8P6n5e=u)6Lz(>>s%|@NihyO7jww(KxH76Pv)?1xF7_+g0+mlbjxr& zU(-i>FMJ1b4To7Ic*;4Vi3Fp+y|j#$;o*PgX7$P0YS(@TMtXLfG~Y213fX$Rkfbej zSu$6Uq=YL*Eq};Qj`eofy9T_@El<05>Y6%M7Kg<1wXc%#Q_8$*J}ou~zt`=KaX#mN z+TStG9)|a)86}u0Xlq{XBrL!7@vR5;{ec6FX1T;^T}i&R<*WTW@rr44i*H8P5}3lu}YNu8%Rq~-jy3;B6QguE4JtN8fsW+anE~!hHKqcg}m4r zC`Pf-jO=J7v4eurE7B9kHLkn_j(xXhn(vEa1>JAu1F(DJ3x|UgFG;l#4u|LX>9gVS`2{@BjeM&ax?<>q_EF`-eknq zQuP9JZLq0Y_G=CREsc1Wb3DI$;xQeI@2MlgZgPIW9KIBkY)80qUsm({hl}`;jiJPJ zKmSFS%O&I9-e2qw6%kkPaxY3SRo(iY?~{}LY;7{K^vs6C1Wwa|M3ETZ=l|KJXZLH~x`GJ~u}MWJV;W=c?ImeJN5%sd;+ zPwC^OAE$#9##k-nBtv6}+{io3!LfabD1!FvNt;jeofh!5N3Vy(5Ve5uY>{AWW+EcJ zCXGC>wQwIR;LkmyfV*MExo)J9{;}wOinX1BcC;pXDs<@Su-YOIL`>9C&Uv)}37RS$ zeATP4fE}CyknWHm9f6GrV~9?290#UsO$azWp%rb>OsCUAhq`d1)}rjfaNS+;Ar_3Q zT>H+O0a0Ejbnaq4A~$=;%^^@T*zoaDYh=aZ9yvf8lCoqxg$^|5C)z8G+YJX|by&1C z!8OblZLYJ(ZSSVd^}LWY_j<4xP7w-XP|R!$_+{&up%f5;#gL;FzWPe`+!F5YPnn?I zXw}Hl;L|6eibVA^S(==+@_=~z<;!24xC5@TTe7axIe*czq&z>f}(MwM!*nr4SE zh9TJWI>9nN>T*1Y!D$cwvI{ay4Uk_MvzTTQ^2)Y12TqyjuIykaa(UB z`yzg$89QwbjG{tiQaTxTiv)E@Ij*k@KNiV(=JQ>}v|y)dgx4ZhBM5ZYoGW)45I4)B z&OVCEew_l^TI$Y^55@A=lcAvDJ$GWB$@s#Uq!E;(4njBuz(_<0;*XI?@wlM(jc6Hb z6cPU~Ew&UEJ63E)H6;n{M#R>O&{!_V!-?AR+K-SbAnv-JA%jCj0Tu;KgrTO=+l!=s z>R;mPgOgas7OYC{hHFkUhP%blgvO=cVu*KlPrUGW)#r&B?#&EpUD6{^Up+-INq6qw zVE@E6ct~oYb+}B5G9eX9lqa5TVh~sDEGqC(5={>3PY-Pznw z;2f^-u@{z8euGU(h5iFKfKvbnB9&0GkM;zKVK5-dXk$BTess0N26Qstu2|HQH$0#F zoWVwY&5{(}3%b`9T9|*g+_(qvd_4k!WBK_i&RFW3JGF_VvLg4%S+g}tiENCiMls4>5oHFME0P$+ znb3njbHkt=#*yVy5wvdj!j@_(cm`4|LU#EaPCAsd4S+1^wuf&8WfISQxlhymUshq>gvN!!0gn1M64@qUF4P*P z1P7J7=(~LVIL|?4_dlB>qiaWRJELaW^G(P$r*_I`+I-Qp-}hrYb0Eu+&2+=cF`LWb znN!wUPe-SJ4-P*d&Si1#){=r=-RYnuyXs1WH0MK4!B8t->%MO@zJNh~r)ApWcg68u z=UZA$E864|ZU#@qZY+b+6XHR}FQAXo+jd_hraa6*l(_lZviM%F^4>3~EQVmFO2iPC z$(HZzZ&QbmKAS~Tm#vJVQNe!YMbrX_nkm?=W`0QJmk*gZLF`Cka$Lxqi~h<}*VTD`CcsxKQG!h4 zwu^|MLcge3a3Lf>y4oAdW4hdB*{mFFh?oKHy_w9UEr!^K24>{>0-6^L01Ds?`LFZe zGU+rc$S2K+hCEf6Q27w2LpXux6#NpR~iv7gt-@x51MjOasG1fPxyb} z3UsxvWYB%5c*kL6q&oT3a>;k|CxE!Fp>6K<`dIKM={~8={7L^vB_j}z!YZZZmU~$h zGo;hJ3!ldA5H5$5$Mav<3JG904-WZ=?kdiDH2b8`p)@j>?0=nX-e5I!9NzChr#77n zg%)TI!3N_R$nS?+I}$}a@t3Pu5%~{SVe#n!9+5e+<0z8L7gxP-cKr9RA@t2r7;Nin zrN&>dl+9>?WYclyPb%EBGJ0t>SHT&QYu+FogQg5WmB1f z9*aAm(&spz5*!J;D?s(QqAqY*lsotKuo9iIFET3-ggw%`|K+ql$ECwi3f+FIVqI*5fL98a^5@Xv!&pc>Ujl zFD(>ljrt*50#nR9%j`*8ZMghJ*bK+h@<+H~KApnB#1sJuqevo{+9gqUiIl{xFQi}4w?+Nv12gYpb^Q~R^P#*) zRvh^lv|412k^Go?guz0e`7j)=VAa1Vj0q9#ArE}kfNFJW#IkHG3FP}xVKkyJrA>yT z@E3z9nU_CV%`J>@Zs|tF|V!QyPY)J{d-TfC3=b$E-NyuR^=qNy>jVsbD~@$2xR%O+X?H!xSK zp740>*BDWum~PE(Jzd@T%oyai@R(n7CS58?c<1{YC8u?QQM`1dEN}ej4DGZYT|8gC-oD59p_(gWh(b{U2)^ zHst(ZcMP_%n}nO7aFxdyFU{7?iWi z+)2rHbB1fQPI;>p?w7ccLgtHtZQ-J`!r?ib(xqZYWaS!f{>^D_B;f8+qLOEXwB)tt zf?N#b0vJ1XtSGon!54cvGQ(NDx4m-`fre3dTkfx^kfxC`-k6NoAm|Pw{|+%?-30zE zB@R?Vlyf0**(Oj?>g&eAX(DaOY>mlI-9CJ9s>V{OPZJ=j>&hkQwf?RtG5<8_G`mzZ zPIgR^uSY+*_qk&q_5}8RGe9U3o_9#b74uDH^pAilIvInL#b7DlF)iN#E^O1Q09Y)` zBARJbpSg8j$#817`B4ym^>-KGX}FbX=CK;$s>-{x{IZ^r;2C3MYCAyo2rCDguWa1i zdGxZqqCYGEC0^S@6E8bo*8EC&L(^xfuCwJ@es(+K=loK&L1pT&@0t5qHx{EQGBtal zK$o}|7iM08fYVOl=ohbn-0jH%S!{>ycRy%$`$ZA+z~rah(X@H9;)4(7%4Z$VrcYr; zfodHY^+;d!CH_tk3q@iq-gl_F9_+$kJqX7YBe=m$S8D= zO0$}IH6i9byTAdsrme*W3lm2CTzx_6Xtaq8^p85^B_g0oV6D?aQpW01uS5S6x2AQo zxw?tI8D|sIDHl?;F!F#1*Ric6avcFtSFLWR-IAGU?Go&-%&3Et0ehXh0J6^q0dxqw z+4$4GR2i*ZaQFTSw{ucJpH;nMMV;E>PfFe5ES(OO`WQNf)tp7|!Sm@v*_RDpUFs5( z(3^TSCP}~J2F;R5DQow2tw!zc9$uJ5^ofXofZl+g^Wjw|IZH%6M?PnPRq1#&JL6P1 zSP^0RPUZP8#|UXL05KJ=dv4BL*&PgDdmxA4RZUXSp|Ch~}Y^i5zz20?Pxm_c&xT{#2qCjatF#idlxWW7#3SsJg*Wc5W);Q1lJpuT} z#%*>el^1N-;U$V&XQG}(qET)Q+db)xo|yw6U6W>zqT|J&jc?9@q9dYr=@U8b>Kil_ zaanx_26r1DZ(RcNA9F%-GcLHz-l8dWp8m=WNiZ4sqrM-D!ips1(p^zlHv_HSlC{V; z#26~UfIMr%Li!l=ShTiy^oW z_@ROj?uoiIw+*_=6MS5VrnIl&bi&TQq8BTY;bh#~65a7SM$5N;gNPmiPeyMa(WS*QQ!0e%kfbFVM< zol~!Lg973V(*L3vmiY!HR*HBV$B7&zxBI;Jq|#iRs|qz-#P4L@Zg}!^I8`KP;dCDT z?yb69eGctA-N8uy&}5^=sJJuIROaHHu2`+R;w>|FO$_*kEuB2&*`aeChO{?dwp3!N zS;`A}cgwiydaf8~fv!{xv5OuXiHbrr%+?V^h-eXNb>BcdtaRAK{&+5N{Z+!$AjI#v zeD3%5w|;1vGa!IgSRAEhO+ZSAxs%7Dwwl5NG$ZbJGoA(#}*B+LLq?A;YLq_ zA|=%KGh@D66WaFBaYMUg%0^KF8h|2%^=4pU!eafUD8&3F7eW%=SiOe}y~*5K4S#L@ zJU^YT$f@lk0ob$q=(V^qqJ^5!19#b@1V3wCUM^Mk({rx$D}n*GZ{QHJ{Hwy+TFTV*KtH{nz>`j_L+%tuB;w8e z+l^dI8T_E{>}Gt>Yj19lUr;f1+NwP#DdAx@KPj*g1JCtM+32`?hD7>!AawbX;&7Te z6cF;v)uO!tM$E>NbQf?GVU6wYZe?xi9s&;T-#7}NgqeQ5BJ+GtN{v+xK&b(GeED<} z1>`dxSZAdlI|fYKG@52*j{Bm>m8Cuk*mXSvT=BkZkeMwk7PLP;q#v%dvjyX$!OFpf zO2?~jD7HIIr^lI5vB^KnZBn)5@vr-@d4`z^k~t@DoknpA{Jp<9wRoHU7wEF7BCg69 zUL*1Q8pY<<`?1;cZ(+@~N@XLO2WZ)G9_qK(qn9k`8GyOLuv$rR@$L- zJ&~ld#o=)=>1xJz|2Po^W>Y$8&Y{+OSwqMYF@-%`OQ!4{;#fWBQFnV-v;THrPwQtu zv$#J=l_-Yc8l0`7J%-9>Kb+RAEuQXi>a4u3rAgNU6SKQ9>o@yL6`{zz_Dt#Z2~r|i z6zG*Cb&RZTpgQXLWRJs>Vfh$KFXXaO&tMTn%xUl#ocS(e(B7kF%U-nAuEapxO zb-kxINg~`rR(TWlN+BhXHku?aC*$}u1WuFNMP_p&6|(KWDYBBu#hWV`Nh-v7uuJwF zkk4&$6VajEo$Y%#f4O(QPR$na3dx@d*H0hwy|0cydF?yn?nTXu=)8vTbIByFymc)G zt~(v{<6ui!ew%v$i=nffPLwUKNHtf5`$WX@%i9m-s;3@H=Inp|o-~@GwLjHq)TyjE zUwwD6qE%qCHH6}Y@v<2Hi2Gsg>glUt4{Bu7ZNtkH_ELDZ%eW1Y@y&`W-cB@d&}6oO ztGe}Dq#4%3g{l>s+PJdmlpTPc&&R63K+e_0LWAz!8uLL-VTCS?wbDU=r%NUB$8zvX z==V=D5zv}-%cr$ZePyM4;EidP5b=V=B!!yn@${=_RYW;0GrM5~nkmehZdv01mi7djE#*4(!6Bz_2bn5&Yr=!rL354(i^80}d@ z2p7(ci&t(0pgVB&7u|_ zAWU~jw?5CCk#?Gy@$&EI1}yUcO)?J1YAwjweBsYP^67LYsW|W@L}n&_P7Q{AK$Lw$U#Wu0J&xraX|KRi%l|1Yz#>Z&+96`qlTM6E=d&zG2qtBw z1R}YUVkm@YCjkk0yuz2eh>3!0byh~ZxrR?)2e3P_u7MDuy!$o`oB&|g4&$`++FZnz zJ6P=(d41ULW`*lpI$}ALG5pKe_}oUrO1!=8`%9${Pq0D@>=tS`i0p5yYJcHk&v0N!Qtt~fb8#TGwMG# z>z<++&hgBoIgzyQ#D)_BjrwIDG89J%u9@tzWdRolZen!-AdIHc_jbg?eVyC2WJj_tV z`^2lwZSy;E!qWR-aoM&zQ`)Pnsa1!E5HqFRH&j$t{jqAVzZxS+JUY)KY=J>_SFVJt zTwBU24>V@952v!Y$J#J#^)mT3I;Ha=n!kcTn#Rni9bdhSh$+;scj}Fj(1c)Tn5B<< zROUdgp|MZ9!vW(7RZJo3;O+ieexBR!zrC#OZ;@~ja+)osITZjmHnp>=^}gb{4;5g) zLac)#?a5wQ5~!JA4OoqII*Aw5MFs4Zp3EKuY$uownV?8g?T6&wPhGqyq^pnr z^fkmJjAoaSrvmzkf9sZTl^qzVSn|^t)IUBO?~bXWmestLXgD^pxj=~S`3RUu>Bp3C zB^wuxw3uG{lkg82?^%rGW~p9QB8p_xAXl;@{wUx^SJydx(T3vfX7~EtDj7QE@)lRJ zt^Y-0MzOEp8#UuCCXZR^zu;wvOzh;*6J##$Z9vlxU|BhU%aXz{B2D*TI?$QR+*-=I52H6a zrg!Bmv^3&VA9{HvLG;gD_!~&3Xs(d3cgEpfsu`cwWdVvd;jeRe{?f&zOg(G1(8xVLLKcE7~jiS4D!b zG%>2wNvhXZfQ2fa4p#7ze^^eemXob|ecGla-U;(6GGaFERp8vazaq}xEh*A~%Tv)L z5$jgv{o#B*K99qW-K}XkHlyDu5i_;tcQF8{SL3$Z*X6cl9BE)%&%23{7VI>va>MPx zGcZKOz{tz4Yt#88D>%0VwOWZ0RXWF|U_oiV0|G<}Wg?TCey(4&5Ox7V^M|J>=?U{e zG|LfS^+lp7Xa=-@OsjV*%}L8Vr6N1Q0i^6sgu8v&f-w(8THnZ4YY>^kzrfHHp$=EbE&=TWaq)k1Wpe|4da6*U1H_1`bIy_=VV08QxT z+>)8Q=3s5iEtpqgm~r#mD%l5WA6SPpum3}f>ZE*aOiKkN1&z_8lx{TTXyOs++C}X? zEh#$FG0Cu9lFPJ!*wsoW1*I>lrhCTgVQ^E$a zU&#>@%)5T8r8AxStOP9ABgh%;M%LTvXEut6F7F~@Yei-jPPDb)lC<5+tYN?^j# ztsr<#Cf}!=^+lC3iJzfc>Cb%L#QJHi%>k`Wy>q05i90OTji1)xIQ)cFj!tqg*ZMwf zf&jcdCiU!mcg@8`R>j?uGoU4N^2QoX^Hh;n=T23L^Q5)G^8)mOx#HgDJ>_f;4y^kceE}2I;ZOC$fJ_mA8diU1vRT zL|QghUW}Nn24b?+c3zWrql^C8I}t+Yb5sFT31f!<|K`_?lW3FI_DngM}%zI0_l zUk5rp8Xo=J=oGUou2#&BgRE$^=2^!4KgJ+!k^koZ15!&@A1wtv z=R+@m|KIf^LWLn6h+XaI5B)FRwKy>qVBsGXSW91L_rfw;nf+z&1J3s} zoJThOV5U)HIQ%l7STZsJwob3;iTXWTOlQmz-~E@&s(IX1Fc3NVTlep za2p-@Cij25!~gBD9|+7<&`Ppg^6h`B5dQ0R0g(aU6V*Tc+g8l~PW9(}qWXKeH9G#U zPxlXgqWZtIIi&w*LHv*3`Tzg=zkk#JT`m;GtFM}fuKJwQs? zqEah~403>CD(}m1`ukfCdjlWR2FZWA_ryW7u^j+m)sD$;zpS2Kr>3ax1ed|&>8xzI zBJq1ern(pZW{b(?sVfb*G5=8tb!;zCSosoxqb~$~psw8~6o0%sWqZMvk)1YGflD<% z9PLKQZZ~9ha~_*Xu$c+9Qa1upeN*_tJssGQ{x{CQh9CMk&-*-ig8xSQmo|5{xC8^- zo-ZF`iOpXLLT~sH1O7!fcq&csf$9Azz=O7)JPMaaF#rAxUm^KIjnUwhSWm>uZu^^K zlHdRKL$+IvxK{IxfM*N3j!<5ix3S6sO{nJ;-SDY$1}>tpc?d75aT_nIY@hG(7Qlp6 zZ&c#SwM-gF%K83ckYv=i8+;}7q~#b>5CCQ)6x(QQXsPh93y08#? zMgCM`I{X4YR0Aosxyhz1-;r{im-nJRND z)+?jEKk4Ir>9#e+5itZZ4BM_gX{CWD5g#YAl)viwj z;L+-|IwaURj*gTuye$9>^ba1EQ~pPkLMD992hM&@&i(#l(m#fTS0fBrkgjLIr?Yc9 zFPH)=A737=KrEUspR4>K1_W{1B7e_;q#Dp85AtDwAnlby$145P?n{Uy*f|={!{rW` zYH~ixNc4C%x}#Q$7_)uALjayqt}!Z=MH~ zA}h^Mw)@MKfWw;|RlORkaeAc;{;u>r5%0f0fA3;vteXA$+>`4f{03AF17^TLa+0#B zlFU*2V;kLXv$TbPdl?|y=<~xfJX+uE_x{Hcl|!iIU_+D-6?*xFUABH{+}ZE%JTJ0L z$CXkIivBu8silNnwW_8YJ4mhEx0OfyM7-@c$S9{ZB3pvLs)+Z6!lU008NXLp1Ahyi@&&))z; zE2=we%+9-J>KbfTbUnlH2iiAjA;9u9F1G&4F46k^-oswyzP|ghCz8nRp-W~rfLdp& zD3M$Hy#k}J>>OHU0Cg_`o>WxWU)MkZ_p=?22#6SkUkYdbrdCNKyZ@RQiI4+~*>tOh zA(1A`6jdzkSk1UlJ4gV!HKeT&M=6?rvy-dRtkMZXhnDD>-Pd z4@ec;`B_|@6YVSwNg%PWVc9YN3Ap@zT4}UhgWiYE@hCD1kM<>-dz%KwI(uKabkcsm zV&Of?%2k*OhQ0`2_J4W&7u&AtWZ~lqB(zqeU3JB46$1xo^g2c)3$b4OOW_N9fT79Rr;5z zYEB;FN3t;C4Jr%)Olu2bml5sHW(0A};37)!9?`g-Mic=_kyvQ?y>UtfP(O=M|@ z&j|}#UxQC$TDRV3ua!=q@%cxg%QLuyD0@Y_=23v>)|d`WLk3Z2b603&^3>%Tu=mY&pCQ7K^v)&L#cTHA=#xd;U)MFh zjg>twiN&oJ>&Hm%Y>mlz$HR0%JE_cAGJJmrwss-|=j~a`i%Fph#em;}G?XC2RV0(N z*KY~YzX4jD+xR9!g94u3j$P>T?d>F>JJPzqy-Y zP#o|$m@{d*vTwh+vGvY2`0v-mxva@f_ zV)B8sq?@F5gojhVTkNsTk`K%p0e{#gQ>xZk{=5sLjV4t+$`YYZ;0)z4*@6kwIo_Th z$k^-D)$I8VkHi>h6p^Q#$C3zSxNQ1qy#UTgF_^bad%rh5|1uP ze_H$&UXi2;!@#Zm=H7$OK-3~XTYO@nBBqyOLswL^*jt!5*!iu1VP6!J$yc^pmi{wq!=fyE;85(+N@ zqMQ0^X#FuMgAR0g$Ipl7-LX@onyAl_9~M>5ZBMAlhecXlM;$2_5c|#7rUYt^;J7jh z_uDgS$`;?UfM9U3tX~fMn?j&Y-5OuaGX3_RCg>#23G+O6WKI{#X?ji|%hr6&@5r;( z-%PkwZ8GaOi1#Pt(r!NTay)#Di7e(v-(!tMvF+JFE9TW&okWo~niV-nc3U|cyQe#b zvyMA;PUER!ez0YTC~rI#g+Re2(QsNR(R12Yqj~zxeyo{IT^I4EkWrPcaKsw>Z3l%6 z-Vd{DLi1IMHD#M+Qlbz*$ykBpB<57Y8Qsb@8lb3V$pTogZX@Y@VbF-0=Z*bKb7M-Y zl;3*m$5FVrCH$-l+UQ=m?EOwSl3_jUHaa_NMe_ENHgN^Lq9HvJ!_pv~!;F*+k?QI> zB+e(!mo;NU4hA1hZ;!jdCjP06TWP3woz0da#AIYnKr+TxGK)d!>Kn*cq5-J!@X0>B zg&y7nrZKX3vP)EQgX-5wjHLFmytb_8Pdl;t4h^$}l+6b}Ot|YeyXBN;nwA1-V{SQs z%h&Gu4sA4xi7g2+?D3$UsK~RJv#CMqwr+~4PU!yY8Y2PyRhso)gMPJ*zzg461jURD z>2*9`u`9rfE2qi*?(sNn1Uyr3l7D1wdGAv~AJFDoz^K1~4L$l+L^}+-Am{X?ZIfCHS8sc-A(;W<+g&ImsO-{CG9R)A48Mab=T_gw$H>O%?*$5*k%WOYO=Qvm(Q?@KLM)tXhKYL-zI|j=y zai0Xv$9RX6isDxVN@OpOv4VQ=aGb52mq5~X=S`RH?)0i>xG3c7E)%_@ zwn@2R0Id+sv9BXS>J=XDB_MQ8t&||6lfoSIP6)S&u*4viwjiCh?R3>BnkX}^lJ8C9 zO4qY&CaJ!66QWl;^-f!_%6cH#+`*!A)exB&6R>E_O0SJ9i;AEai)5qlo z9K4XMx#ssT4(6?fOQy9C$i$ps%igzyt7RJ1&ooL^VtxppfDV>#am)0ZNA?U$clK-C zPLN7`UZIIT>p@rQ{&-$HL%wxFIj5aioPysOmOA^!d;t5?!SC6ofVdlz#P+1E2X?yWDUX zZmus0&Xlq|u~Z6MT(xC;UV8iTlqSO4FRZiXTAuVO9rfXH2gQYnpB{6Y$QZDP*CdKR zzdpDzDzx=z(cIEXuHvc~GOupb=G&@EGYVKWSy zH%`3X(Rg8pwbko$3DE@oNwhAn|Ayn6&__o8&96`7vA$us%(Ibw2p z?Awc5*_T;wACmv>xONPYjZQ?vvp={Vd@#3Yvst2z6o*N_oODX57yGV8?GErg z{fBcf2%YBZtfYS@E%aYkJQUYX35njxLs5Bw;IzwI-GQng4>H2%fc4zuVf-yNc|LMj z>|R?6{wLG0o6WdBllhKSG)O;D<|vYQOXO*a4j_bWRZQXq)Ewckc#N&if^o(ZE4$y#Pw7b(7uA zmLPO;Ad;tslRBHtw{Qd+&oka3n+D5XMz;jyV`c2RgKYVQ1eC zrE=XOqD-1t9W{o$J)SLF5hpik+MzUlUT^2OFtL zeAPNi#Hmv0H7Pi7nscPR|782d(X#R5 zcFnv+X`OdwEl|HYQsM-U0-w~fRzjN|v|`okbf{#XuD1JGh9l(l7+&4MnyyrUT{Wrv=7b0DUZ>Hchr(T54@VBt z)PD0tq#J+AULYuVN4&Z%B0`yrWFyD$j#v)L zMWmlmLH3bYRWs0l)uCTAWb5`B7uU7M#;?mQ>Dr-CoVA#xb>@SIqN}z&fsHAt6U%k8 z0&Gmrk`#j`mH>~{5jo)OHMN4S7e}-R+>)MVvL)zH2j+O$%opxF>8nP_+L_Jc!-^RJ z`5s0J1hnwP*jRnUYU(lRM)&fy*r4ncs<+3Yd`ldd4v&4w1KVrWm5Xh5d_?>{OQ~=q zl^|%C`k3Bf)5Ly`d_YV8=quk1G`KSB{nB>j@$MMjB!C@2N_c3ncDrkZz47XL3czh+ zT)kOSv0cqQ_8|YR^TY0sdx+o8b_9HOth+Ow2BJ1BcSU1-bFMK|N4or# z>{3_i)cGXC_>Bn>^YcyLk$S7Z9%LLRR%=|}yP#`1))hwnkpOXH@?xWpfM(VZn75pw zNEvV`(-B2KXDDA1;fu6I79r7^8aSm8S3<8%yJJmIzH2D}|TntEdaov1i>VnRZN*TFMm9UkB?nq~Cv?x0}uu1(^P#Bt^;Jy?LyHpRmi z(qw|TKPUqra$Pn~==k$N&Ti{YbW@|dnN089?T^l0@6gN1J>Fu6KS)g>4PLGos-7Mi zkchbi`nN~D>TEYe`KXsCUz_fPXaDX@QI|~`|A^^4SIFw~fmy+5nLI3YFx@qc$>fOi2PUSd zgSS7MQ>*nV#Yju<74eanMi6XZwr`9E( z51gpc4n*WL6DXhGl!O}0Y8+30+~p2Qs8S&$bhA9ieFF>FfXkKtMxvHDt}tv@VryU1 z{Z^m8>w4_FFhXu2*1Ym@4@|s)>D;a1@U)Y zx@=QcO5OC~-DyUPPlBUq@_?ja1s}ss^=tcY<{#@`;tlc5RU`-^paseTS_o{&+@PQs zq}us@Ui~)n`^Di6F>b*=ueI&8%x8Fa6a{Dn=MPV$rPlT;+n)?^EzI}OsL{sEom7|G zuAtV(KPZD%2On9Q_z3{C?O63=Zz>yWhvm!8Q_3a5y7vxPKD!C{+17^6rp6t=ywqFM z1jBO*~_mv|lmmV54cH74Cx2eQBmCp}~3alm8k z>}&8n8F46xMMwe*qdHGm>^~2~9@1#w7ysG{`Km{p0Xkv9>A*HI3}1`%7~E1)jikTS zl1f1#7S8UePACn{c6vH!=WSmi5v34z%B7!;*q*&OiRmDJdn~bbuzQHXY;bp|sB72# z`f4n~BWm{@Dc`k$Tl3 z;Rg<8QJA=uq;Fu4=Z$Q7kMhzm`1+cV`>w35`a8t_liaYR+a4+J?DTDAGBG8V@|Lo} z$y*MmF`P6JXx8-7RhBMFvCvj3e2k(N;y~oo9>}cCAB$sz+ckW;IxEIrp3T~aI!Xxl zP^wwqmJvo`_7vyugmR9~Mo*y{8eRR_;7}{AHvo(NkfC%4(N}0!jR06gO6>Pxqix|& z*virqwt~*se&oS8AOP!hmS8Luxy90!anMns*ctdmMufYD8-Ke)m(2)ShNwTUPZu06 z5pfZqyIM?~QJl19jgni#N`g!6qp)1Wo6;pc9$MeL$v+K^qP{wH88IWk z2@x>`Kr=5A2vpthK|ILXAt4IKxFDRLNSt)9C3G#0#ZAYCx{#0%*Tt2uC)_OXhAyv1G(?EmP zc2ZW4I*9!0+h?CMv}Cv`Mri~sZQO`|2dFM@@F}C+F38ScHRAA?)l?;HmmFBS9dFo z(IKAWTKv;G`$)^2}` z)#qw7ZoHThe>P_tD=_o++tW~AgPmZHU9JMf?$sat`9yp=U6^V!?#*YoL%c-asY%7( zv29c3MF$58)5LxGgp@tQ50oW&_?z{Q-$MsW%a@{m36MPDaHY0C8(yKK&f@23HsIbF zNzZeK+lF=nOYbQ4J@Wj?*;qmNravt(9lx!%#8)ORxQ(~)x zyM4Y0W#}6zt+ehI3_*x7^gCs7-{p~?CGgh}fo=*>M3`~IVtu+{&j@)NQbnQd5=IIQ z_BUih2na;=PhJ3mo-Zi6J8U7%NkiwPVrmI9qEMRhrS0NBSB|(N_=mn{H?7V$t?ZY( zq3~RL@2y&aFuPVNr#@U?sl(v|*Uyd>Qly1ny$dRS%fjI4RqzUronu_drW(K73w&UZ zqKJ7zNQ&i8amqX8^3Lk)9m=LoMCSmsXr+dMkC|wUkkhlDU9!zhzA|IKDX2S)4)(Yy zz^MD}$00eh;_}hA&TR>l=v2|6)77?Pz^iuW%*(+63##`c~+ku-+-w_5&N8FPjVq0E7;u0`YyNTaOmjzx0rd!O!{YY;9C~ zYc)M_<(BREINJM^g&&Gu#0_PwVbRu(ZVr75jxg>dQ?38NM1+8EpWd!MngM9B?B~BL zqf&z;H)wko$v|Xva0)2OWA9so{)n+D|@OkU`u+rh|4Y*BxjiCZ9qQyJ6lq@hi1 zn)Mze6#@6B?o1zE;$R9`;GvkCbR(m0IZ;i+#XaJSO;HA8dXXz}T9Ie7@a~`FN0mx2 zC<@qwiz=oQKUS>ZJ;9^&@WSQTrqcDayG$H*1t>c~wVvCnJ?t5z{>NUcqIW#@ZST8m z-7l|V$aAWRzAi^PVfuU2?#znkZ@b~2riVm9E>>b2zL~mzdG63SQRNeGbJ6@ZM*B5T zEezWE;wi75#j0%8?$YQwf)|}ZVsbHLh)}H0*g2>Nt@>>b^Os?h1|zXRVY+UZlQTO@ zEaoabv&fipR@*iby}IBSK1nQ|wEo#Ye&Zzm*f#Y;hD&YJ+7>KY|GW~qSd!DjCm&7t zD2MN>M~ZES%5@L8&8bdWv~`Fq5-j^ezLC)Z(In%h_-;fF3^)VCR)fJbtlHd$D650k zGNVVc^+dJS?o4o;`BOWjW~lvaF`nw7#se5OT5+&4P~kE-V^1Jv){sVbHlupT%9cV51ov0YX3PLq^xnWm&=@`AiJZvRa>jYIKo5JD{x~q(# zIak%(Y+R);s#{6$ZVU)Rb<|L%U{>7s5!T*B;!R-ii3f4)aiWac=D4<43(k*jsegni z96M8(+F-Jr^>_L) za|j2y$HDJHc5M@uKW^`}!2|ggRP4@}>=s2E1bOY6xid4Hr8@k>9Q@VO9d+Hv3cS`T z>2ca!Z=zxa_8zQYkCW}?YHkY-H=NO_ZP!Zqmhr*qUr|pxpGZ|Q)oPZO;@ato~B#Z3wHE)PQ!2YJ@+JZPUnkb6B}usN=H3{ zw-Ufp8iLvov39mQuYcrCZn|W&gHDFsw$mVqQm+c3ZIQyTB!3innIc`uq`^m1ctaUA&akaeJ|t!bJji4V zB}~w!xbDNIu+6vv7eIOcK<6`-jOjWoq)&L+TbyHHUkU$i-lsLVk+Qw`?A=``XLiJ2 zQE3?;D4vyYMe*HS9MEc!zg#L@JFB#s9fOTzi*^<>IY-k2vyDG3{SSY8<1aiK9CuW)$!{#&hK zlZ$@fuE^iC%g9Rx=Dk_3^e?5J%Jqg{eRH|)@BLdHMO*M*Jlp}7_rBbu^wSWo%OPS1 z-8+{1&8>vY;{`t8}#VB}{szpr%tXwHv#YA(0P7Hxj+wmFM3gD@cJ z^2v!w%PZ>MT~*yTwr>#+U5xuV=UpXy(NnTX`Ctn`eCkV*tJ;+NwqUt&%fgWzFFp%- zv7~vI!1CvWb|YuCj$U`qQ|?DoKY#73t?%ZT(CdmoX_vdwUxPvaJ{uOa*(daUT#YfF z&{R@mU^*FU2oZv``a1$>3f*FN>g=6?xtKlZkRKMSdm;I^ao1mmJ@oA{t^_3hjB0F( zkWF*yD3S7SXavEe#fDyRtn8BaSboD=u>^zVh{3wr*G}MLNCGkS1-7Xricv(G8+7CG+NDFClwuW$tvC_ z@v*cYFUTD0);SdNwDU8#bkVf?uor8#KFbuoF80vicV5eDYa0Q&tUi3%VrrFb277~8d793xMj!A!sK_MFF8gmbf&tpaAQTe}0bx##xyqV|^gw*-T9Mwm2v^v|Z zvZM(%m_d~mg;D6?<6WcC7a4HZQRmoZ#{9?)BqRpZUQ3OMjRhT{=^`8 zN*z95c(n9qczeAwQxLPH_(NRS!$}_nPvL~_I=bWYNvUHmZs!ct(J2S-*!JRn=|I8B zUKVi1hn&xZPm+c*1G|n^fu2Ax^zh|SM%^uz|GV5?x_w5_G$|kR2sOMwgpX$1HSKmh zw@j^rJMI?C#^HYd22&*CQ%-DfnN8dP+R8M+1WX$IqguH-DpWcZ33N zq?@uC3XU^_G)BsoZpZWn4=As)vqj(fJ-c4!GCD`vPd4ge!8T$z>-p{4blqZ&&paDu zbbsJ}LPNLHBbR8}f*b6ODlGPJ@1onmH6zu57ecYi&Hzd2s#?LreAh;zLkuD8*8Mb@^fMenJDQX!DXJ-$EVSVM>) zo+0ry`j6_?C8pZ&k?J#hrkr~zr3~RW_;j>7>p6csaPL-WWK{>a4MPk-Gzbpx z`W64Tu3M~QjXUEAotQudFJ9bhME7hU)_4-vWChHOWb_jL>Y`D-L=&?$?!;+p$ZhY8 zh+Df6JB9uRJQwcfDNFsWbEVCcj>pz{(3ed?1YI~R*B?IY2iGUd`!}Dqt!#9*%|0FKg!CJw)@)5I5~xEV8O{(sP^&5X3&ZLf}Gz;Oh$?ouyf#h$+YAhah*u zqA*}u2dg80z=vbb%l|~^Pv)x^JmQ(peHX4s!ng8!vanOmaNX3gmqT@Fn4|6_msTf| zo8N;|O}6o$*}a{Iy-rQ!z53yF*D}fy0nPGo7g102Ypea#kAzyMB)Usi8`bZLF&xhN zEE`Ajt%hO&lYZl+zx<`s66E5fHhAP{Bf=8DT>WXRusZiS(mkDENHGA~@#MIWb<>j) z&+#txJUEjee04Rq?;c4vCgLcUgJTxy^3&jo;dB_~Y;+QX*B3g9!od&a6TQPiU?5p+ z5epWe&!Wd|Jvl8Hebju~baQBydp9uA$EuZ5v{Kq?t`X=>5jD9iTDDFq_E<`|y;h1s zkaCmk*~;EpltrV56z8?}v#_w#IAbmH>t|i*l->gk-gt%074|Uy1)yCpd!)z?%O*Tr z9kFdsPYV7DUaSH^pa;@_TS3#+GyuvP_7T7rh!0%tA?c70^oB7;nhw~49w!s8zZb|i zQ8~0=uVjn3k|eP~_w)OvI=vAzL7gCUQ~*NVMLVY%3f)%U-n4-D-_vg298g~=y&~Q= z{8zI&u#v#l2Vhk*7BN#tb%+=*%_nWKo9Nymg->>9#c8#=6x)t?pK(-Sy?dO@CjYRd zGg3}$#bx}S`gK&cRhp1PyocjORI&d$0BV@B(#%1&UM08rd8;nWw51@1XRH*v^0oIs zmS_GHzM^9+`8cqpQuohc@@b+X6K3%~4?#}j!BL_?#e%5CPvZ}NTPM8l#f&|jl$Big z?E?o#%KsM1QO13N;B@pL)ij`VtlD2OybS51ybsWnQ2w?Aga{scJr`X1eStU|=yTXO zJK#g{1{SL(F}YA~E5>w?c5rrsV*&4%MtBqZ407K95sM)jRhY6Eoa5_KyjJC9Y^&(v zEiA69Gx(&S1A?Hx9Y1J78N$3uTF;8kgC3?nIOzWLOyM^#fb6>2kWLo7q!Rz8?@1!( zlPS2h`3!_cTiP zyENmiEcPzT&4IxxF)FWuY;y^_?K(zxBV!+~(LSw>@LgO>mb?acw+Ncfg)}YV0t{`M zKcjQyG0PR z)X?$ztIKA@RI9Bed1&^mF+}XIDL(C3Z}icvK3}-4Ui-3oCA{QJ_GLDNmwEuYw-ebE z*+#1%d=V@&{&=`1!>F@ZPqJ&FfZcxSbxL%j&D4PFo59v3!!!>tp**bT;yS1J2gL3_ zmxp(t!Z0Lh>_5+C4TFUS`weINf)Q?W|khdp#2OEB^h5pmcO1aji z4IjtRczo^kRCC3s3=OddZ3-moWW4(pnH437s!FR;YuM6^F3<@UyABYPikPvwtw*z3 zHAxeZlRpnU&&$@q>5pH3N$?4b1nba`K)6$0ZO07qsDak&28jKM6FRf@B&67y#{mVo zMoeKx@8)dUBK1?gkDac#=fxxy`aR+OQS#VX$?AqSf9UzxOn09^ldArKztPZ{KsW)|sU_Ky@ zz=nAClmr@5M9^%Lo5PQFp@iAntKGNExTzHg24 z(c=2H%RK4@WtL@9B*nYRXd@-D#MLPLX!tBIazc^YAPSWPDu3~QTd zn=)r7o9H9g+%DZ*Er(?|)v+oaM7@*OOhpl$kx@dIY}l?Pt()j&5!$h^5k{_W43+TA zK!rEk9TL=fdc;zQwL-N1q~!noO>>;351+>=Yhz+>#HI~@W9%QW2#cS;EwmK%zF*Ee z_gO``blR%DH`tte86=biU$4Xbbx8uuevMaPQ3yWnef#xR_6;^Z__yKX%?AT=HwYmZ zh#M&|ociv)HH2L(Zl~3H`G9;@ebafXluGfi-TwH^GRvQq@>sVrq1*5=XD@PbbL^~{ zMHWO#vnXhUis3Yxs;=^iulPa9eo*>hCmxwm?hlQFBF%fO56`V?t0@0AbZuYuAH|3* zSMD|BFcTg#|F9e+BuEHfq$dS!UGcpD7Ct3H?t<^t!!48G9Oc%~dhD%kc;%|u2jGke z3JRwTBzk8xdc1HJ{#wmAHLShvVs z*N_ZZI!*4U7ftvFgh7~-89#xMvesBO9aG=tN_3QXxO%-tg&iJFXrU(u1pmQ`ql zet=Q0gBmFwP?Gcq?LQv;&-3pLWe^A%h*%ABFd471Ida)bk=&?{TEZ#g;&J6FI2lLRQqKE*gl zDvRv?IVBuvnD}S75rL~NFzNu}b}8{yHu#G_oupmZ2_s6T)zQ6Cd@u%Z#-zIn-}AdU z+g&K=3A+#llpX%#{akR@wsp2t{5!fNN9y-CJl21*W3OURB^*@#^|XIKv;!Y#KGMwT zuN;!QwRtIL=EqYW2M`BgC?5y0$Un%u_(UEwg7bt&tz0Mx0j~TV z2gkwk<8E14ZD(oZX>Jhs!}EE?6DMH98`F6#jO9T8?M_xUMYR^jb7*sP;HYb>3@KU& z6GBPtO%2vS|J~gqcEYN@NZg}(}=OKhu6A}dkn&v0U zO^sQBUBQFC27#LLoF#79WA}ssFnG=z^n2-rU{^qS^nduz82IjPd866sbqqk@>FVdQ I&MBb@0NgRdssI20 literal 0 HcmV?d00001 diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts index 50de4458..d664d6d7 100644 --- a/packages/web/src/actions.ts +++ b/packages/web/src/actions.ts @@ -201,6 +201,22 @@ export const createSecret = async (key: string, value: string, domain: string): } })); +export const checkIfSecretExists = async (key: string, domain: string): Promise => + withAuth((session) => + withOrgMembership(session, domain, async ({ orgId }) => { + const secret = await prisma.secret.findUnique({ + where: { + orgId_key: { + orgId, + key, + } + } + }); + + return !!secret; + }) + ); + export const deleteSecret = async (key: string, domain: string): Promise<{ success: boolean } | ServiceError> => withAuth((session) => withOrgMembership(session, domain, async ({ orgId }) => { @@ -441,7 +457,7 @@ export const flagRepoForIndex = async (repoId: number, domain: string): Promise< await prisma.repo.update({ where: { - id: repoId, + id: repoId, }, data: { repoIndexingStatus: RepoIndexingStatus.NEW, diff --git a/packages/web/src/app/[domain]/components/configEditor.tsx b/packages/web/src/app/[domain]/components/configEditor.tsx index 28f47f58..bc876ad6 100644 --- a/packages/web/src/app/[domain]/components/configEditor.tsx +++ b/packages/web/src/app/[domain]/components/configEditor.tsx @@ -14,15 +14,17 @@ import { jsonSchemaLinter, stateExtensions } from "codemirror-json-schema"; -import { useMemo, useRef } from "react"; +import { useRef, forwardRef, useImperativeHandle, Ref, ReactNode } from "react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Schema } from "ajv"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; export type QuickActionFn = (previous: T) => T; export type QuickAction = { name: string; fn: QuickActionFn; + description?: string | ReactNode; }; interface ConfigEditorProps { @@ -46,80 +48,74 @@ const customAutocompleteStyle = EditorView.baseTheme({ } }); +export function onQuickAction( + action: QuickActionFn, + config: string, + view: EditorView, + options?: { + focusEditor?: boolean; + moveCursor?: boolean; + } +) { + const { + focusEditor = false, + moveCursor = true, + } = options ?? {}; -export function ConfigEditor({ - value, - onChange, - actions, - schema, -}: ConfigEditorProps) { - const editorRef = useRef(null); - const keymapExtension = useKeymapExtension(editorRef.current?.view); - const { theme } = useThemeNormalized(); + let previousConfig: T; + try { + previousConfig = JSON.parse(config) as T; + } catch { + return; + } - const isQuickActionsDisabled = useMemo(() => { - try { - JSON.parse(value); - return false; - } catch { - return true; + const nextConfig = action(previousConfig); + const next = JSON.stringify(nextConfig, null, 2); + + if (focusEditor) { + view.focus(); + } + + const cursorPos = next.lastIndexOf(`""`) + 1; + view.dispatch({ + changes: { + from: 0, + to: config.length, + insert: next, } - }, [value]); + }); - const onQuickAction = (action: QuickActionFn) => { - let previousConfig: T; - try { - previousConfig = JSON.parse(value) as T; - } catch { - return; - } - - const nextConfig = action(previousConfig); - const next = JSON.stringify(nextConfig, null, 2); - - const cursorPos = next.lastIndexOf(`""`) + 1; - - editorRef.current?.view?.focus(); - editorRef.current?.view?.dispatch({ - changes: { - from: 0, - to: value.length, - insert: next, - } - }); - editorRef.current?.view?.dispatch({ + if (moveCursor) { + view.dispatch({ selection: { anchor: cursorPos, head: cursorPos } }); } +} + +export const isConfigValidJson = (config: string) => { + try { + JSON.parse(config); + return true; + } catch (_e) { + return false; + } +} + +const ConfigEditor = (props: ConfigEditorProps, forwardedRef: Ref) => { + const { value, onChange, actions, schema } = props; + + const editorRef = useRef(null); + useImperativeHandle( + forwardedRef, + () => editorRef.current as ReactCodeMirrorRef + ); + + const keymapExtension = useKeymapExtension(editorRef.current?.view); + const { theme } = useThemeNormalized(); return ( - <> -
- {actions.map(({ name, fn }, index) => ( -
- - {index !== actions.length - 1 && ( - - )} -
- ))} -
- +
+ ({ theme={theme === "dark" ? "dark" : "light"} /> - + +
+ + {actions.map(({ name, fn, description }, index) => ( +
+ + + + + + + {index !== actions.length - 1 && ( + + )} +
+ ))} +
+
+
) -} \ No newline at end of file +}; + +// @see: https://stackoverflow.com/a/78692562 +export default forwardRef(ConfigEditor) as ( + props: ConfigEditorProps & { ref?: Ref }, +) => ReturnType; diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx new file mode 100644 index 00000000..df425788 --- /dev/null +++ b/packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx @@ -0,0 +1,415 @@ +'use client'; + +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { Button } from "@/components/ui/button"; +import { cn, isServiceError } from "@/lib/utils"; +import { ChevronsUpDown, Check, PlusCircleIcon, Loader2, Eye, EyeOff, TriangleAlert } from "lucide-react"; +import { useCallback, useMemo, useState } from "react"; +import { Separator } from "@/components/ui/separator"; +import { useQuery } from "@tanstack/react-query"; +import { checkIfSecretExists, createSecret, getSecrets } from "@/actions"; +import { useDomain } from "@/hooks/useDomain"; +import { Dialog, DialogTitle, DialogContent, DialogHeader, DialogDescription } from "@/components/ui/dialog"; +import Link from "next/link"; +import { Form, FormLabel, FormControl, FormDescription, FormItem, FormField, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { useToast } from "@/components/hooks/use-toast"; +import Image from "next/image"; +import githubPatCreation from "@/public/github_pat_creation.png" +import { CodeHostType } from "@/lib/utils"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { isDefined } from '@/lib/utils' + +interface SecretComboBoxProps { + isDisabled: boolean; + codeHostType: CodeHostType; + secretKey?: string; + onSecretChange: (secretKey: string) => void; +} + +export const SecretCombobox = ({ + isDisabled, + codeHostType, + secretKey, + onSecretChange, +}: SecretComboBoxProps) => { + const [searchFilter, setSearchFilter] = useState(""); + const domain = useDomain(); + const [isCreateSecretDialogOpen, setIsCreateSecretDialogOpen] = useState(false); + + const { data: secrets, isLoading, refetch } = useQuery({ + queryKey: ["secrets"], + queryFn: () => getSecrets(domain), + }); + + const onSecretCreated = useCallback((key: string) => { + onSecretChange(key); + refetch(); + }, [onSecretChange, refetch]); + + const isSecretNotFoundWarningVisible = useMemo(() => { + if (!isDefined(secretKey)) { + return false; + } + if (isServiceError(secrets)) { + return false; + } + return !secrets?.some(({ key }) => key === secretKey); + }, [secretKey, secrets]); + + return ( + <> + + + + + + + {isLoading && ( +
+ +
+ )} + {secrets && !isServiceError(secrets) && secrets.length > 0 && ( + <> + + setSearchFilter(value)} + /> + + +

No secrets found

+

{`Your search term "${searchFilter}" did not match any secrets.`}

+
+ + {secrets.map(({ key }) => ( + { + onSecretChange(key); + }} + > + {key} + + + ))} + +
+
+ + + )} + +
+
+ + + ) +} + +interface ImportSecretDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSecretCreated: (key: string) => void; + codeHostType: CodeHostType; +} + + +const ImportSecretDialog = ({ open, onOpenChange, onSecretCreated, codeHostType }: ImportSecretDialogProps) => { + const [showValue, setShowValue] = useState(false); + const domain = useDomain(); + const { toast } = useToast(); + + const formSchema = z.object({ + key: z.string().min(1).refine(async (key) => { + const doesSecretExist = await checkIfSecretExists(key, domain); + return isServiceError(doesSecretExist) || !doesSecretExist; + }, "A secret with this key already exists."), + value: z.string().min(1), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + key: "", + value: "", + }, + }); + const { isSubmitting } = form.formState; + + const onSubmit = useCallback(async (data: z.infer) => { + const response = await createSecret(data.key, data.value, domain); + if (isServiceError(response)) { + toast({ + description: `❌ Failed to create secret` + }); + } else { + toast({ + description: `✅ Secret created successfully!` + }); + form.reset(); + onOpenChange(false); + onSecretCreated(data.key); + } + }, [domain, toast, onOpenChange, onSecretCreated, form]); + + const codeHostSpecificStep = useMemo(() => { + switch (codeHostType) { + case 'github': + return ; + case 'gitlab': + return ; + case 'gitea': + return ; + case 'gerrit': + return null; + } + }, [codeHostType]); + + + return ( + + + + Import a secret + + Secrets are used to authenticate with a code host. They are encrypted at rest using AES-256-CBC. + Checkout our security docs for more information. + + + +
+ { + event.stopPropagation(); + form.handleSubmit(onSubmit)(event); + }} + > + {codeHostSpecificStep} + + + ( + + Value + +
+ + +
+
+ + The secret value to store securely. + + +
+ )} + /> +
+ + + ( + + Key + + + + + A unique name to identify this secret. + + + + )} + /> + + +
+ +
+
+ +
+
+ ) +} + +const GitHubPATCreationStep = ({ step }: { step: number }) => { + return ( + Navigate to here on github.com (or your enterprise instance) and create a new personal access token. Sourcebot needs the repo scope in order to access private repositories: + > + Create a personal access token + + ) +} + +const GitLabPATCreationStep = ({ step }: { step: number }) => { + return ( + +

todo

+
+ ) +} + +const GiteaPATCreationStep = ({ step }: { step: number }) => { + return ( + +

todo

+
+ ) +} + +interface SecretCreationStepProps { + step: number; + title: string; + description: string | React.ReactNode; + children: React.ReactNode; +} + +const SecretCreationStep = ({ step, title, description, children }: SecretCreationStepProps) => { + return ( +
+
+ {step} + +
+

+ {title} +

+

+ {description} +

+ {children} +
+ ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx index 76c19a11..fd7441cc 100644 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx +++ b/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx @@ -1,26 +1,29 @@ 'use client'; -import { createConnection } from "@/actions"; +import { checkIfSecretExists, createConnection } from "@/actions"; import { ConnectionIcon } from "@/app/[domain]/connections/components/connectionIcon"; import { createZodConnectionConfigValidator } from "@/app/[domain]/connections/utils"; import { useToast } from "@/components/hooks/use-toast"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { isServiceError } from "@/lib/utils"; +import { CodeHostType, isServiceError, isAuthSupportedForCodeHost } from "@/lib/utils"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { Schema } from "ajv"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { ConfigEditor, QuickActionFn } from "../configEditor"; +import ConfigEditor, { isConfigValidJson, onQuickAction, QuickActionFn } from "../configEditor"; import { useDomain } from "@/hooks/useDomain"; import { Loader2 } from "lucide-react"; +import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; +import { SecretCombobox } from "./secretCombobox"; +import strings from "@/lib/strings"; interface SharedConnectionCreationFormProps { - type: 'github' | 'gitlab' | 'gitea' | 'gerrit'; + type: CodeHostType; defaultValues: { name: string; config: string; @@ -35,6 +38,7 @@ interface SharedConnectionCreationFormProps { onCreated?: (id: number) => void; } + export default function SharedConnectionCreationForm({ type, defaultValues, @@ -44,14 +48,21 @@ export default function SharedConnectionCreationForm({ className, onCreated, }: SharedConnectionCreationFormProps) { - const { toast } = useToast(); const domain = useDomain(); + const editorRef = useRef(null); const formSchema = useMemo(() => { return z.object({ name: z.string().min(1), config: createZodConnectionConfigValidator(schema), + secretKey: z.string().optional().refine(async (secretKey) => { + if (!secretKey) { + return true; + } + + return checkIfSecretExists(secretKey, domain); + }, { message: "Secret not found" }), }); }, [schema]); @@ -75,6 +86,27 @@ export default function SharedConnectionCreationForm({ } }, [domain, toast, type, onCreated]); + const onConfigChange = useCallback((value: string) => { + form.setValue("config", value); + const isValid = isConfigValidJson(value); + setIsSecretsDisabled(!isValid); + if (isValid) { + const configJson = JSON.parse(value); + if (configJson.token?.secret !== undefined) { + form.setValue("secretKey", configJson.token.secret); + } else { + form.setValue("secretKey", undefined); + } + } + }, [form]); + + // Run onConfigChange on mount to set the initial secret key + useEffect(() => { + onConfigChange(defaultValues.config); + }, [defaultValues, onConfigChange]); + + const [isSecretsDisabled, setIsSecretsDisabled] = useState(false); + return (
@@ -88,14 +120,14 @@ export default function SharedConnectionCreationForm({ {...form} >
-
+
( Display Name - This is the {`connection's`} display name within Sourcebot. Examples: public-github, self-hosted-gitlab, gerrit-other, etc. + This is the {`connection's`} display name within Sourcebot. ({ )} /> + {isAuthSupportedForCodeHost(type) && ( + ( + + Secret (optional) + {strings.createSecretDescription} + + { + const view = editorRef.current?.view; + if (!view) { + return; + } + + onQuickAction( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (previous: any) => { + return { + ...previous, + token: { + secret: secretKey, + } + } + }, + form.getValues("config"), + view, + { + focusEditor: false + } + ); + }} + /> + + + + )} + /> + )} { + render={({ field: { value } }) => { return ( Configuration - {/* @todo : refactor this description into a shared file */} - Code hosts are configured via a....TODO + {strings.connectionConfigDescription} + ref={editorRef} value={value} - onChange={onChange} + onChange={onConfigChange} actions={quickActions ?? []} schema={schema} /> @@ -130,14 +205,16 @@ export default function SharedConnectionCreationForm({ }} />
- +
+ +
diff --git a/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx b/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx index 786de4c2..c63b2263 100644 --- a/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx +++ b/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx @@ -5,10 +5,10 @@ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, For import { zodResolver } from "@hookform/resolvers/zod"; import { githubSchema } from "@sourcebot/schemas/v3/github.schema"; import { Loader2 } from "lucide-react"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { ConfigEditor, QuickAction } from "../../../components/configEditor"; +import ConfigEditor, { isConfigValidJson, onQuickAction, QuickAction } from "../../../components/configEditor"; import { createZodConnectionConfigValidator } from "../../utils"; import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type"; @@ -17,14 +17,16 @@ import { githubQuickActions, gitlabQuickActions, giteaQuickActions, gerritQuickA import { Schema } from "ajv"; import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema"; -import { updateConnectionConfigAndScheduleSync } from "@/actions"; +import { checkIfSecretExists, updateConnectionConfigAndScheduleSync } from "@/actions"; import { useToast } from "@/components/hooks/use-toast"; -import { isServiceError } from "@/lib/utils"; +import { isServiceError, CodeHostType, isAuthSupportedForCodeHost } from "@/lib/utils"; import { useRouter } from "next/navigation"; import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema"; import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema"; import { useDomain } from "@/hooks/useDomain"; - +import { SecretCombobox } from "@/app/[domain]/components/connectionCreationForms/secretCombobox"; +import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; +import strings from "@/lib/strings"; interface ConfigSettingProps { connectionId: number; @@ -38,6 +40,7 @@ export const ConfigSetting = (props: ConfigSettingProps) => { if (type === 'github') { return {...props} + type="github" quickActions={githubQuickActions} schema={githubSchema} />; @@ -46,6 +49,7 @@ export const ConfigSetting = (props: ConfigSettingProps) => { if (type === 'gitlab') { return {...props} + type="gitlab" quickActions={gitlabQuickActions} schema={gitlabSchema} />; @@ -54,6 +58,7 @@ export const ConfigSetting = (props: ConfigSettingProps) => { if (type === 'gitea') { return {...props} + type="gitea" quickActions={giteaQuickActions} schema={giteaSchema} />; @@ -62,6 +67,7 @@ export const ConfigSetting = (props: ConfigSettingProps) => { if (type === 'gerrit') { return {...props} + type="gerrit" quickActions={gerritQuickActions} schema={gerritSchema} />; @@ -76,16 +82,28 @@ function ConfigSettingInternal({ config, quickActions, schema, + type, }: ConfigSettingProps & { quickActions?: QuickAction[], schema: Schema, + type: CodeHostType, }) { const { toast } = useToast(); const router = useRouter(); const domain = useDomain(); + const editorRef = useRef(null); + const [isSecretsDisabled, setIsSecretsDisabled] = useState(false); + const formSchema = useMemo(() => { return z.object({ config: createZodConnectionConfigValidator(schema), + secretKey: z.string().optional().refine(async (secretKey) => { + if (!secretKey) { + return true; + } + + return checkIfSecretExists(secretKey, domain); + }, { message: "Secret not found" }) }); }, [schema]); @@ -118,25 +136,99 @@ function ConfigSettingInternal({ }) }, [connectionId, domain, router, toast]); + const onConfigChange = useCallback((value: string) => { + form.setValue("config", value); + const isValid = isConfigValidJson(value); + setIsSecretsDisabled(!isValid); + if (isValid) { + const configJson = JSON.parse(value); + if (configJson.token?.secret !== undefined) { + form.setValue("secretKey", configJson.token.secret); + } else { + form.setValue("secretKey", undefined); + } + } + }, [form]); + + useEffect(() => { + onConfigChange(config); + }, [config, onConfigChange]); + + useEffect(() => { + console.log("mount"); + return () => { + console.log("unmount"); + } + }, []); + return (
+

Configuration

- + + {isAuthSupportedForCodeHost(type) && ( + ( + + Secret (optional) + {strings.createSecretDescription} + + { + const view = editorRef.current?.view; + if (!view) { + return; + } + + onQuickAction( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (previous: any) => { + return { + ...previous, + token: { + secret: secretKey, + } + } + }, + form.getValues("config"), + view, + { + focusEditor: false + } + ); + }} + /> + + + + )} + /> + )} ( + render={({ field: { value } }) => ( - Configuration - {/* @todo : refactor this description into a shared file */} - Code hosts are configured via a....TODO + {isAuthSupportedForCodeHost(type) && ( + Configuration + )} + {strings.connectionConfigDescription} + ref={editorRef} value={value} - onChange={onChange} + onChange={onConfigChange} schema={schema} actions={quickActions ?? []} /> diff --git a/packages/web/src/app/[domain]/connections/[id]/page.tsx b/packages/web/src/app/[domain]/connections/[id]/page.tsx index ccc79f88..77a34c55 100644 --- a/packages/web/src/app/[domain]/connections/[id]/page.tsx +++ b/packages/web/src/app/[domain]/connections/[id]/page.tsx @@ -149,7 +149,9 @@ export default function ConnectionManagementPage() { currentTab={currentTab} /> - +

Overview

@@ -219,7 +221,15 @@ export default function ConnectionManagementPage() {
- + { router.push(`/${domain}/connections`); diff --git a/packages/web/src/app/[domain]/connections/quickActions.ts b/packages/web/src/app/[domain]/connections/quickActions.tsx similarity index 65% rename from packages/web/src/app/[domain]/connections/quickActions.ts rename to packages/web/src/app/[domain]/connections/quickActions.tsx index 565db033..fc54df7e 100644 --- a/packages/web/src/app/[domain]/connections/quickActions.ts +++ b/packages/web/src/app/[domain]/connections/quickActions.tsx @@ -3,8 +3,28 @@ import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; import { QuickAction } from "../components/configEditor"; import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/connection.type"; import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type"; +import { cn } from "@/lib/utils"; + +const Code = ({ children, className, title }: { children: React.ReactNode, className?: string, title?: string }) => { + return ( + + {children} + + ) +} export const githubQuickActions: QuickAction[] = [ + { + fn: (previous: GithubConnectionConfig) => ({ + ...previous, + url: previous.url ?? "", + }), + name: "Set a custom url", + description: Set a custom GitHub host. Defaults to https://github.com. + }, { fn: (previous: GithubConnectionConfig) => ({ ...previous, @@ -14,13 +34,21 @@ export const githubQuickActions: QuickAction[] = [ ] }), name: "Add an organization", - }, - { - fn: (previous: GithubConnectionConfig) => ({ - ...previous, - url: previous.url ?? "", - }), - name: "Set a custom url", + description: ( +
+ Add an organization to sync with. All repositories in the organization visible to the provided token (if any) will be synced. + Examples: +
+ {[ + "commaai", + "sourcebot", + "vercel" + ].map((org) => ( + {org} + ))} +
+
+ ) }, { fn: (previous: GithubConnectionConfig) => ({ @@ -31,16 +59,33 @@ export const githubQuickActions: QuickAction[] = [ ] }), name: "Add a repo", + description: ( +
+ Add a individual repository to sync with. Ensure the repository is visible to the provided token (if any). + Examples: +
+ {[ + "sourcebot/sourcebot", + "vercel/next.js", + "torvalds/linux" + ].map((repo) => ( + {repo} + ))} +
+
+ ) }, { fn: (previous: GithubConnectionConfig) => ({ ...previous, - token: previous.token ?? { - secret: "", - }, + users: [ + ...(previous.users ?? []), + "" + ] }), - name: "Add a secret", - } + name: "Add a user", + description: Add a user to sync with. All repositories that the user owns visible to the provided token (if any) will be synced. + }, ]; export const gitlabQuickActions: QuickAction[] = [ @@ -156,4 +201,3 @@ export const gerritQuickActions: QuickAction[] = [ name: "Exclude a project", } ] - diff --git a/packages/web/src/app/globals.css b/packages/web/src/app/globals.css index 7d38a5b3..de099e82 100644 --- a/packages/web/src/app/globals.css +++ b/packages/web/src/app/globals.css @@ -117,4 +117,15 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} + + + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } } \ No newline at end of file diff --git a/packages/web/src/app/onboard/components/orgCreateForm.tsx b/packages/web/src/app/onboard/components/orgCreateForm.tsx index a56c5b10..e05c9486 100644 --- a/packages/web/src/app/onboard/components/orgCreateForm.tsx +++ b/packages/web/src/app/onboard/components/orgCreateForm.tsx @@ -8,7 +8,6 @@ import { useForm } from "react-hook-form" import { z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" import { useCallback } from "react"; -import { SourcebotLogo } from "@/app/components/sourcebotLogo" import { isServiceError } from "@/lib/utils" import { Loader2 } from "lucide-react" import { useToast } from "@/components/hooks/use-toast" diff --git a/packages/web/src/components/ui/popover.tsx b/packages/web/src/components/ui/popover.tsx new file mode 100644 index 00000000..a0ec48be --- /dev/null +++ b/packages/web/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/packages/web/src/lib/strings.ts b/packages/web/src/lib/strings.ts new file mode 100644 index 00000000..9fbcfd47 --- /dev/null +++ b/packages/web/src/lib/strings.ts @@ -0,0 +1,7 @@ + +export const strings = { + connectionConfigDescription: "Configure what repositories, organizations, users, etc. you want to sync with Sourcebot. Use the quick actions below to help you configure your connection.", + createSecretDescription: "Secrets are used to authenticate with the code host, allowing Sourcebot to access private repositories.", +} + +export default strings; diff --git a/packages/web/src/lib/utils.ts b/packages/web/src/lib/utils.ts index 52c7fdb7..34834bfe 100644 --- a/packages/web/src/lib/utils.ts +++ b/packages/web/src/lib/utils.ts @@ -126,6 +126,17 @@ export const getCodeHostIcon = (codeHostType: CodeHostType): { src: string, clas } } +export const isAuthSupportedForCodeHost = (codeHostType: CodeHostType): boolean => { + switch (codeHostType) { + case "github": + case "gitlab": + case "gitea": + return true; + case "gerrit": + return false; + } +} + export const isServiceError = (data: unknown): data is ServiceError => { return typeof data === 'object' && data !== null && diff --git a/schemas/v3/github.json b/schemas/v3/github.json index 22dd4cc7..ec4a9f4f 100644 --- a/schemas/v3/github.json +++ b/schemas/v3/github.json @@ -33,6 +33,7 @@ "type": "string", "pattern": "^[\\w.-]+$" }, + "default": [], "examples": [ [ "torvalds", @@ -47,6 +48,7 @@ "type": "string", "pattern": "^[\\w.-]+$" }, + "default": [], "examples": [ [ "my-org-name" @@ -64,6 +66,7 @@ "type": "string", "pattern": "^[\\w.-]+\\/[\\w.-]+$" }, + "default": [], "description": "List of individual repositories to sync with. Expected to be formatted as '{orgName}/{repoName}' or '{userName}/{repoName}'." }, "topics": { @@ -72,6 +75,7 @@ "type": "string" }, "minItems": 1, + "default": [], "description": "List of repository topics to include when syncing. Only repositories that match at least one of the provided `topics` will be synced. If not specified, all repositories will be synced, unless explicitly defined in the `exclude` property. Glob patterns are supported.", "examples": [ [ @@ -106,6 +110,7 @@ "items": { "type": "string" }, + "default": [], "description": "List of repository topics to exclude when syncing. Repositories that match one of the provided `topics` will be excluded from syncing. Glob patterns are supported.", "examples": [ [ diff --git a/yarn.lock b/yarn.lock index 016346d6..ac27c28b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2106,6 +2106,27 @@ "@radix-ui/react-use-previous" "1.1.0" "@radix-ui/react-visually-hidden" "1.1.0" +"@radix-ui/react-popover@^1.1.6": + version "1.1.6" + resolved "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz#699634dbc7899429f657bb590d71fb3ca0904087" + integrity sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.5" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.2" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.2" + "@radix-ui/react-portal" "1.1.4" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.2" + "@radix-ui/react-slot" "1.1.2" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.2.4" + react-remove-scroll "^2.6.3" + "@radix-ui/react-popper@1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz"