From 67ff874dd3e762a877bfc1fcbd7ffe21013be71f Mon Sep 17 00:00:00 2001 From: Elijah R Date: Sat, 6 Apr 2024 02:26:35 -0400 Subject: [PATCH] init --- .gitignore | 3 + .npmrc | 1 + Config.ts | 3 + README.MD | 2 + assets/favicon.ico | Bin 0 -> 79432 bytes package.json | 30 +++++++ src/css/style.css | 3 + src/html/index.html | 109 ++++++++++++++++++++++++ src/ts/AuthManager.ts | 189 ++++++++++++++++++++++++++++++++++++++++++ src/ts/main.ts | 170 +++++++++++++++++++++++++++++++++++++ tsconfig.json | 112 +++++++++++++++++++++++++ 11 files changed, 622 insertions(+) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 Config.ts create mode 100644 README.MD create mode 100644 assets/favicon.ico create mode 100644 package.json create mode 100644 src/css/style.css create mode 100644 src/html/index.html create mode 100644 src/ts/AuthManager.ts create mode 100644 src/ts/main.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9287d0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.parcel-cache/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/Config.ts b/Config.ts new file mode 100644 index 0000000..62d8506 --- /dev/null +++ b/Config.ts @@ -0,0 +1,3 @@ +export const Config = { + APIEndpoint: "http://127.0.0.1:5858" +}; \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..2197652 --- /dev/null +++ b/README.MD @@ -0,0 +1,2 @@ +# CollabVM Authentication Admin Panel +## WIP \ No newline at end of file diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4fb2a414aed2421a920775ebfa62265504b61c08 GIT binary patch literal 79432 zcmeEv30zHU)b}xzM57F)**s@VL?L8s5E&XMgb>nTs0c-x2pNlH3T22$g`$#XDl!xy zga$){+WTAUdGcYT9`QBMgWsFzzJK^RNE7^yVMB*(_-F3=pZk=8 z+_bq5a_+EXWv8+aVC82ev-z3HY+lA~Zry_GE%yH1B=+9jMD||VO*R+Tof~XU>UFZR zQxe#$Yf?j9vJeGgoBDbcl4kxknHE3gB5C!jrh$G;tO$FCim>f4q{-QJc>@oCGZ zZiB100-NmBhE3i=*X_;n>}^ju_Vz|O_SObi9$VKVnCGlT||4h+&EeK#!Rhag4fE=Pwu0YHm@_8c!D z4S)}T7myCA+Z32&?>0aSpo5n@lf1bNi*!JK_?8Yp3#7w3;D@zRun0e}i9iG7hnr4t zT`kGnaFi7C1LXw?2I+vl_OJFod%6jmd!iGi0igrZ02a`I&><9f;xNcYh$@qLkmm;# zAunVg9e5h-hW+nSWRMr`?(D#%?STCY>A>-Ws7xR~Y(*Nt{b%eOLd1Necgl`Wa;D_SzB7k+F1Cp)pZxR4IVfe(&>JODo+FXV)& zGdvxH{6Oize6JhQgO!-OC=(nnP?=C!-Tzex(j!ZCMX?%4}`h_=)m&=)sf5{-*%uQ z8+8Mf3DlDy6I)rnJ(=8 zGo6|Hr#mzEA}9@z7jlnlGE_HE{fPXKg>nJ%kcs*cY=uxRgmge1sl*^Jr0)TK;OzwH z24W|6fLsKET%dmRLzzH30s6s5lnzJ(SZFKIPH?(`lL@pD%=bDn3H1Z$NTF^3{qS4+ zH#cSTqPj5m&vs?+pY6ijI}Lom^8(7l36KXO6R0D3erTiv(G65jGMPYwOyq_A%G72C zQ{BMJL^`J%kPdeO!B!G`30FVh1I|tmI)F?hqrC(jDJm1l4@5swTS4i7`Vp4UUZRdf z8{s0w8c+T;|6Abx&vgSD;6fU}0)A+uL+3`_0QxcaxCTS$Ky(Ajg&NaH1MnRB;V#HTqi*2s1hJV6?>7*B5c*HlZ{Xwt>;&kB z4OAv>p>6>G3GKx1?cbEmkM7RqUxam@(t*$*68Het4|N05;YTuo`VssFYA-=Ifd54F zBkBgEfruYTZ6?}Fuot|&op)Mwyz1MfG04<+;=VgH=}gnsp}^uOiI zX>7se9!$Zd?o57k51>OghR}h?L|2pv&<&s;K`wYZk$Z~T%OC3o^dmVtLG%LnOgtTk zZU9<*??dha9^m~35nr14PhcyEex&{rgT8b!kqPjhywH#2WrDMpoNh=0TWLD=_xAtb zN>8TXa!>XF(1Fqb=mZ1v0MKg*1LaJ3gKpg~Me z;00Khdawo2zz-l3c|eDJq8oq@P$md3puI#}(S^-D#py>*CWu^c@dWyj)PJJ7Av+ZP zNZ<$bqlvA+m;&`9PY02>is*(8oNmClsy&Gc-)h!$wlKaA z`!Eh@aE;OdX#g_8K8WeTARUk&Ku01!FnOpWg}MRk1?mN2FFCp3_yKhT`cJvXsJ+ZV znIN$h*h`GHg|>p@1x`oO_!9X6{3o!Nu&7M%{uAiPMtdnr2Ze8K1@Hpq8-BU}*|7hF zzJvyZ4nTtkvAuvFdNC*$JTIV*6x9tv9eJ)RlNZV9NJ59sEU}rym*(uHsBYk7g7>Af zK}RAlpfAnI1(PmfCx~7kbO2ky`O>_-M43Qd;Qc3(Yw&5y@VtO|mtXDQ8utI_WMkm z&|Y%3g3r5fIuhe-YA>k|nE}2u#@U!}5at?0eCbA+06&_GvwwI0kCXZ_NP|Z=C>;tD z`g|u7s2_Qm;PoTgO3rUUTY)k`<$~Bt-fsXulHu(o(tyNOT%1jFE{$=vXlzAdZIW{# zegp9#K{t^2G9BY=AsuM$3G@SrDMWop%)|Zq`Ojb<-_l|qC26sg4!{RD`Vu~PNaz5( z5Z8y(k-eCQS3xe&UW({Qq(cF(8_t6)pbyE}if?qoU*ha6NxX; zhvIZ3@&XrIrHRM{(F?+uqCLq${%Zeo=geTAB=={K2C$e%N&VQziG2w_piCeyJS6&& z=tu_b1nLLWk30={dr52s@f(CX5_JO~Q_$E7^&|RE7+<1HATPiY#@V1BXzq#n(j@=H zuC(J+bcA_z+AbFQG&TmMASR3=SzqS9=0qm2M{tThRZ9)h3Q6k6#>V+GGABcXS zbZE2_$O~vQiEcojf%pv~z7zTl94~P8lEm30zC<4i_~G73h$}GHKyog`cLJY*`j9lP zqP#%uCFUWi|Acm;ksmm}0evUpH&B`Qo&C?VFlV2p4P>9*>CZe(9e^}Ieqf*6)?yxm zObGb_=umjQ5Bn$qbYwir1fc`)17|0Y4(K~wM&F6pOX5T3UqC(CjUlmBBQFp-5Fe7u zH{9c63aTH4{DAr~8+9bcRV20|c7n^t@^KZ_k)rV>#uS*B|JC!y{TJPZ{Sz7h9}r%E zE1?6>fanI`1)d*JCUBul;6fX5t&s*qHy{l-dr4?<5$GZG8-OQ7=)m&=pL+tCK)(U` zfyu^v17{~hb8x)xlm&hyjkBq4_%`>X1pGhc_x7JLh%HJV$Q0cj#L;0OM+f8uN&})F zkRQ-a5L*Fqf%Xz;Pzd@_=sOWRLG%NODTqv9Y=yQ0c!9>*;4?tJp)tM$--*k+@Oj9_ zIGfXv-`h)!tr~p>AwTf>dE^DG-Td}{ipN%fO0W_d3p!* z0?^?x$i!o$0W7qaLYW}Cf!YermnQxb^%YQ60(4#CLHv$OQNde4NeaAxSqmpXUu_fes9zLoUz)WkQ4wMaU1J9|E zegpNNIGG?mB+>!>2H*!?M>fWn;6tL#4{K@_w=rAP(K!bd3=6N1Z1K2;630^l49a)qCx)S*T z?IkQ;CeU6!2H)uk_>kZ50_p~gFNu!i?Ip(9$PZl31?2)`Y#L|N930XDd}-c)B6$}s zz9hOq=u6YMijT8N?g?oDI&!AjZ|vWCHd|6SjD7KN7+dlHX~6LU@Pkk$I9}lG1mOiv zNAh$4-wAz4;x`Z-iT*UiReVfAeQ81m>N}zTM11LO(@3!V;y7kHVVbl`O)$^_AoU@wJq zxJ&s#$P3h$7RFUVd-jHXrHS7_;|UU1art?YdqO&Jv><*1 ziL=p{1|JG+<}9df|4RQYm^+7k`9zm}@pw4U0oO35gyRL^2c{%{D98lW4bSg^Y=CYc zG(b9J4`zrwaQ+kJ1+bSqF901-CLZxVWDKX^t28TqM@^f9Ls^J|DrnDjvZK=>YrybYPGlC=GxXAQNN(KRn~?Wiiqq7i=c) zH*h{=5$Fa=2jB(Nk(}?u>&PF_f#G89Z*>FbOB3IT(~(3^ipT`j4MP8k|9};yW??a-Uc$~<^6yNCv-d>Vi1EB-W$8s?y^&>f-f%;A~=YnxI$+>{7_?`Vb zE?dMF+#kc1mgtf7>KUcO%Oa!!(1P*;)eS(Nc{7ewr(Fuo-DSWYg4egoPG&=1H9qWr++8*Yd7V$rw%75^XE8(6f= z&W2q$&w@o=$Y1d_ztBc}zih2$v*;-{TdRHNPMa zOJ2j(ieHeM$NB^=OMcDdUPIjuYj;?`6e{n}jbI^VC2 z{Ul`ZJ?r>h^fmwYaAol42Jl(`HbwFNMec)t{g*x9J^tYL_}?e{G8i@Dm;E#U^!^^+ zyX)~V=T15HQx{~XFnQQZ4GVgznWz(oSh9}8^>_#ybqubsa#9l*+Cv`4X5Ee>z2ne7 z{Q>%?1ODmz`>%Z7iO@X^uHzxQ*~Yc!U+0nk>>fgc1DFt8d;Fn?*PrEI|7Sn%-~M;I zu?N@Ro7wH}#fq+f`#%1&-{I@Z!=a=o!wSH#*n z-zQ6YWVzm0e9ia73j1QQAC_CR9~OIJX)i2dbZHMP_Pk;*EO&+84eVdw7wLD!9$4sU z<6n#Ry@D>g{&#f6Itazz(i$<3IYo>ik3Yu#Rv`|Z!_@JZsZ)#LojkYwx0B%0VWDD zFa#4O?5_~*!Q=W6vGYE@LaTyki)@~-Z|PA z0(f82L&^8eiS*M#F9_`+74}EDN-;N}ZxVXtxE@;CKZ|_zv->ACrIUb3!MZ!*F95IC z*e42@Zi4NH=RvXk1iLHj4dwf#VGp$T3wosqb{TtoXrB-E#8TWc*BiW9j`aVLelP5C zg6Dxeg+0NIeUH!&?9__%{Qa!gjqmXz7%RSBj5R{&@ysO{5*}{|m?{pBjlCLFHn5)$ zarp#~&h=;TnA`RY*W=6e@Nv&Wgr`W463UcpUk5z>jnNC4{d;dMapdvK;hh zbA8G1Ecm{|pY>W{j~&4>h~S*~x;63@V*bcJa1RvYCG0)sv3@-6lkXLw_+RXmq5U$V zJpp&Hr&6S!5_?0iFNE|`3VW<^AGCK6`>TI-{{)MG^)?ZVH(!Gn#c6S}0ljft&&N0T zEYknO$;P*RvfuQ1kls)#8$6c1aee^T`-c60T+bIi540P9C%>e}i(qpRZzY1U;rc#F zy^zD3QT!o?OBTgn609BVQ$XA$hqI&oDtzBF&H^BP-~22Du0I2w2kqm7-YwCdPQK?I z?8eV}b;$i={g>8&q4tSb0K~^M;)}5cPH^}`e-QDjpBNE8Rs#76z_-q5)>aViUUSGINCncQ>ieQg7la2@-5U;ZOB}X zzvCaQKk~KMZ!nEKK7(NYIXnV~T@&K|`FYC8$2eH>RTRPf;c0Nuh4D)PYLn* zTrU&lDcTRu_pS)>$k5A$eF~_nP~Rd?;j`fPfwJ+lUJdNgDHN@Z1J;&chj`o&$_4I& z!&Fk)_@)m7`f{+hy%9Ud_vul*m^yhD*`bIt$G%nKQ;}YSMtw^$_TT6!;#1L{<)7U@ z*0~W!FT{yb%rJ-bKs+GU_7Mk0@h-x;KBsSKKM7!+I9w=?10?+u@I0trC@LFVe;x3X zkf%s*ol>K3xD#a@`>&}lg7a$rj(_m}Ddv^$4*?7`JP)3yD8`e=?Qobrg5~4z34m$* zYflV53(8a9*l-d5R-~62Y&i9ADNk|x;Qe%v@tX?PBzJcYUn{9E)zu(yglk8k?y&}M+23-bwncK-w;&*Pc^ z=O)DSa5z7@4-pJB!SDdq6`uvcwei?zZXcqu!C{{HKJ!Mu5T3_3Hk|g4(tY4u9_R}~ zADxT+euj0&`$wD_;1wxm5phX~QR91Fu*ZSmk*I8Z%Tpld-|d4w3&iYVUq=t z;)@_w7x#hvJ@k2C-yijJ`7*@&;=aAp*vFT@;jctg^o z0Q-PG4X$s8lMTRBksb}Ir{Gy|{TZA;#o@0TZJ!WpE7Z4CHtzBLYP_FL_5pSrp2g4Z zA2CuCE5_j`5oeEhIT4-`VyXc*No0eUZ-SBIaHCwm4UdhZICA7Ef+44Pu)o;wM!Uhw z2C;qAFXUoaHRfknaSkKSz94v5Q5-1Z?_Cy?KS2w&|t%f?L%Cfa32)c$Kk)J4TqjFuD@F7i~J1hN-@rW?d36yh~)&FH{v)O zam}y?#Bb7m2ZCW0_Wv}>25-Z8yr!^kTm+{`aJ%2&G>L3ruXy9L5MtQ5e8R8p|K*e6 zfJH=1D&Uh5hbY8o17;ubsMxcBGS2Z7-AAL06Z=Pc+R$!@;L@qCf@c9euGgsT!`=kM z-cme1vEejs!ahENFXS+mr$H__efu-4HR1>XS4uI)h_wd1Ilc^>02Tf9LAo*RRgvjo(IS_?g4reslMfU3`ma!$cBi2%f(EzzXIoaAXb#i zC;SX6iuYfN_*lfo0xt9gVnXqG5X?M}5e8hW2qu>HPH?h8da4?6(F7X}JjM6ceWPzd z#&I9$i!|~S-3K2taXmvKz6fAlAxH9e{@Xeyhw_E<8u6Swt`o03u2U4xNwAy*&&gvs ze~jZqzeD=Yqv z{(pY{hXQ{n@V|=!6j>w?{PJ2a5a@yCWxv0&HEJyW{BN$sNRAc)!T)bpmi+lOT@j~+ z%NQ1q)6$2tgZ}*aLxKOz6c{%~kKk_j+V#&cIN$%>xY2t5*YP;x$LI+$K;M5qVwyO- z(>K^59s>pU_qRUiKlRMuyoFFaa{}-|41d-{*|7`fdT`=z-7d;Ok9L@#!r)ctD{ha+feb{|Fx3T+oZig$r_JP+v|6}ei zVCx1p0A|JqY$bE!$qadX{a@X;=)a#i-M9$xEtm%(_!h*va2OYYZxPOcr}N&w$F_*h zc_)|_4%hMxwuQ&Dd_T)wbcQ>=p77VX8x>QoF|NP#v5m?<81LCVO1zbMU&2~f&4c=Cz!7b zVI2bJx{20=Fz-$3rQAGVicR4#1UMs{%sb^~q|>>U!rD9HIVcVSupDF_I?h6ewNmnj z_eU@&WbPDKhr=8_$qVEBHO!|H>;}ngrwg$HP-md|c~ZB+8Lc?qp4P3f#!fLCqIFO* zvs#FApmTZ2eIn+>8RpglZstc=4UB>L8WmrI7Gi#I9u(FJ`Df1ImiYS=)(H_OL+XU^ z%=!8n;sCgLp*S1yn_0@-eI~AldAU42VRrD3umBjV^SO4Kqwd6DeO9D)!_6Xr+%AV9 z!Q3>h1=3nS!7SiC@-t3_b$)o}!kVmbrYScI9&ie9pGj`?OaR1N{OJD39AlD8Z=5Sc za?m360KPWUSPP)foYu$?=Yn&jXbqgqRl{1P2ydqFGwhL`FrWIi2t5h*3uFjq5dR2^ zK;{f{Gd{RnIhplBu?INcgx2gEYs9pkm&wl?hi6XDARx6Pa-YbXjrB-wHYlkLBi^8K zwmi;=#pljriGDN})e`z@F&{3>`H}o9%$-2Yis$ki=P9HGDPaw$e$v38(}RT zXBKi86|NRfuus0gqky%cxF?c#}FWSIffs z51;2gf-{rIJ#uGTaQG%7>)+KAxtb`Cl@sAjGL!u8?vI;iL29{3OT;?xd0-ynz{?O~ znE2eOP==0b(AiErwvRk>xKC~#rbxYz;!L>v1#PoKjA{I!-#YUnthC;?_okhw&d z>&3jgNR1PoIh`>|%# z)bCKgB(o%Wy-BeyKdOaeo)h!6WKJZ`N96M6jn5o%v|PRQn;HgJYvu7dxe*{goD9*K ze_UOU)9?K3Caia(KEk=_WbP--hemse7|0*hipl+9odW0Zkl8bIt^!{dZmiLNS8IXU z0X*hIIHM5h$>~jgUMKZE@VR5X3-u<4A0p?0z&-Nw-Qga8bbq9VO=dyhEV2IVQ_SJt zf_ud|VkCdxS6DCQYeKZn$klvt#sODH5#oeMeG%zNae!oA9_M>-bB5`ClKGju@1gX= zTJJoInOu#U)=U~_JBiLSg6A%rd53aD>$<`k1I}?my-9eJ%t!ue}`U=0*#DU@}J zjYA*lN402DgNFJoKc|k?*|?cx-0USfPlKzmkr{=Ytc%XiAUFVug9F{p+jKHBL&%$0 zcSd|G&=Thl^7@^}BL1k>j5PzS-;lZo%*CNKL8$lh^FI)O!SN=+!idZeZucw?>!-rmFSMRb=A@CifiV9EYFs$`1~E4%=iEG1GVh14af$FI@jXx< zaWX_|jrh#54v(@<@Yk9@oa=b6L20J2#t0 zIR6&)CSp-YEe>W>@t7c?taF$@;vexdh&Vem`v-HUNDUEY%e*Y2GiCUBQ^NT|IA@B~ zadD;)UpM?AP6KC3anD=?zl8HAIb0vrn_MlO_;H-Qj0C;}dj66hR*jN*!^*~Y+B=tZ#&jo9MWS+}6^IJ?|jtk6gfjG#pOeJ|u(5bWE5oTc!LAnqJFnpfp=)bwmo_9mXK3TG$cIhsGp^V42Vk+|!K zFn><_P%&4<^*hqsBt6>-ZG;(egIopssw~U;1if20F*>%v-btd&x*|BG3cRT7`W5-{t8M zt4;H^6r)Z0Fi5_M&(m}H5zIT_*>`wG1z(flY6zH1COKN{LE`ern4cxjggeI$dE@)% zPx8nl&kgq=oCS>e&hPNmq+gBe4aR4J{U6*}LbL~mJ5!7Fbius{?}MJJM|@%b!1Z%xke!5RSOthsZ0NX>_y4gABNGK?EAuBCAp z?StcIpyD$jm~$?_M$bG(Sn}I>KiHwWlAQABE>d@wnv&T>lj2 z`}iCh<|%}{!Skp{PKxh!rhNzST*#R|v=^1sm&o~Ew6^qjc~X)i#W)}Ey24rFh@B?6 zyzY(hJvmc>@~AMML*$h^Q{({9LquLNzexIFxo7gDo+fL+d6L=x7|ZjRTf7fE3k7o` z*xNwPM&hu-BxeJ?S3E7~Im5JOKx=NaKNsYcoDqp<;Xz*#<~jd956kB`AxDFKVThT< zIrEU4Dd2nBNRK3qdr6KMc|(}f1X-c^VR9dQUW=YB1N90{UjH_aDV%%GyALerb!u&qwE4Z9F!J#8>06nt9rDT~cSm#d6r(S!9r1F)=R{~;k>r<14;Ssh9<+4ETY5!R4> z_y0WAk`2%pO`n~B8NL*GUiD|0vnQGb`j zg)mP6V`LJW!uOE#b1@!7%+dPB@4@}>^jjb8{O=?1|W+&49(?zXvdPNH_lTmoJ<``yX*827V8zeQ|S#5F-Lu0pha~|DD5D zAwB@{HR!vO*b-y9HbUHwjalR8JJ?O3SRc}7iGC2^J+Q`xwLt7g1RtIFgE$WX`_@Su zi*Y^0bcn_&DO=%t;2dh?0sj71E;0i94z*JBgXvrbS{LWzX7D>nFD?4uBJl|AjfXe@ zF?SpfaF{@5KJXg<`Ky)~K`jw#@gxSKvlH-p$Qep-&K{{1!x>_Hj)3w2?b9V^=842S zlm|Kxe&#>l(cXmi=7BFu>VP!XqUZErd;@+;{yD%MVXVjduUyPaeOHbLcx(~yz@j;0 zh4;UDnJMkPCcZn=^03Z~J|c->0OQ2tu&}opeRjeF5WjFU2FSDFc!2bO!}p+k^7rqw zd>XB5<9UAQoAY&d5-TCL25N=qdm%=N$1LD^e8>a9Yk1}w!71Pj1H>mBQe_rfju(Eu z^9nOk_eGzR)Kam=5B?_k9_&}fI0R!dIQI*Afb^2`F*4)}NSuzb80mc_vuPOAH~jrO zubfW%TSfeQa?TDPcab;)>&f(-R?;hpF$SKw$k%1LXTxJ{?B_OqzN_O5+Dlsqy_?ua zi@m3Kb_e#H@-=q!Td~fHm?X}Br!f^bql4m6gfZPxo5ozp8mE~>YgU`HE~{q1n$F^W zkY>2>+2BGRa9oPZgk817m|eBlh+T3vKWDf02iCY3O0b(^j}&0XQCgb01L0Y9~LP7znNm+ z-uuMyV}vtpG5^cgN$~og@qM6IqbNTsg~`jf&7jW{0gKciZpX11iC2X@{q0@+-aFd2 z@cVrVUJDhJxQj1y;_4aC^hqnit{3EuYYh#y027}|ZblaOcS>=$Q?N$#EIyAi{V zbNmov555cLxbbuO-ELdnp5pEhe?{^_V6z}61fNUJ^CV|pBjyuxgqSZyzX<(^jk07m zAo@hE6IuvA&u5h)oIl)+<|7e1gEKhrb8+S(;`d28`S|z3ArR<&%i!&xh#V3#9Rf=5I~#B(UxH4 z(4R&B4{bl>+c(T1<#+OtL0+0dyH95K;CbsfTLnKCbEoK!0S$P2PwX(#7JVd+zdWtw zg!i|5TPLB-BDp}Y^CZ{9`4Wx(7nl2kTr8KPr!#TDhuCZ@FZ{eOUe|YhqgZ2tx4eGFXyQSY8-u&WEYQm+Wq^sxH_~_H@m8j$Fg;R$~J^6U( z9`^9Q9Yw$R=imJG#fT#;u5W($A9(XS|9*#KyTgIY+p>X671)3!ZCHOhdDee{EaPh< z%lgihX0}^Ou|9KJv)g91Vz>5Sg(Vbtf+Uf2g%F01-omF67`{RRuIO7#DAuk^SfBv)_UfO*6ax5Df^jK6L}Y`kq|D>m3umDXU0 zej+}-Fs8$^)XCgOh<`+N9_nYbB|a9d*}Y!sLOn=2!g20D%Cb&kG%3*^`X{+%dh6z76b{YbHwT--(U z0@i;?oC>i6-4m%}1uT+h4(;qpW1lq0Jtq$!`jGhVobJXwky;(qFQk8o*V}>1+Ona+ z-6>WSbLn^%IK%;@t^qY3Jja&Qm@q!WS{sR(s9nKYV|>Ub$hB}w=-sR@P#rl@UjNuZBxvFhYPR6!FAMwz)Dd>3z7=2qma_DQkm z#S6pJd!No+BEMC_YNDsv$p9U5Q{zI3gqD?>37*e;Wos7?mu)_3NmS1%ogd7QpWNzm zvVnbPiR0Vy!|&+KO4_b7QpWau!`IS!k0Q5~hu4LCWZkNt+Mm@)QI-j<)GD3b$~DGi z-T$)x917m%sW6|dnc>%s8!FAS8_Z2=9-3FP%getQS=!eRi*S!;Uj(ohuh++4|1>A+ zUEKmfg*BUD{q>ICN818x`%nEEYEoJ-wW0NPl~2momrj3j{n53CX^)uV(E2v@&(!Ms zGq1lEjX%O{_b4x_Enx1Me{L8cs3~`^EoTZ{JnDz*Rd3R(OQ|_qR5ztQ*`a8W?aovutSQ!uk8dtul8fp> znU8uOv+8U5H8d2}IMt^`)yovUEiCn6caPKC$sRO$Qs)>Gx)emwd+_~Iug{AltjCwO z>2yV^W=g~Cm7$9E7V8hZzBn?pSz7znUs`spU*W}W`7+n9Mm)j2nW?Veb)}OK-H&1bt+tn;7*-Im9K5CEEunw`kd}O;@b2q)o9w|c| zJ}r5AEvi&u{o?Rgr*;y91~KVb^@EsA*7X4u4dvC|XL`IZ8Q8v=e!~M7 zrqH3juBbMwvRVI(4-SKvh-IEmUt2A8R#b_xajM=V_o=eBPmSq*cQ) z>phP<4u~D(ZZ*Bq`pfwJs$Opr-V}tY?&};E)9qyFg8D^|`bSxJUO#Q-jm-5K{ijZ=)iM+$&1m<*$ELV>A=k_Ds{r za(5T^C(G~W=v%mI=MMMCA8Vxhq`}^~O6=Ne=}qqy(!XpCi2igjKnDKTCZV&7szr*! z$GCx!C2@9+?*(agw$)Qq3g)Xh27Uf~z<-2d)`ro|Vh3b z@^QbM%CRFq4XawgG*b(ySZZCh#cX-lhR}Lz)~>$xI@_xzQ;!)@S?thI)vv_5roBg9 zl%S@|`Y<+nj&TNe{T)1N(c+1&Zm)-1oTtpu)L*945+ za(zKjks!=^f07O}S;G2?`&dV9S%1gvaz3we0%9u+9NtE5a@m=1!@I?>(&%d=J!bW* zo0=JaG<@}xnril#M_abssG(O)Fq`bLt-Rjjy3vw~>#W1|+KWcrs`?b3n6|;=(>1m_ z(6O-Gqr81$jZydR8c#+aA6U7sciX;k0-52SWd|{_#o6wVqtf+oEK@TxHnnlA_K5Zp)8vlQGG)JfXUJXzDDL zCcf|7D-M-Ek6}N1csNyDuixZRp;hGYXrxM$`poirX~DYtdNsA7dM(07XnhS%{^0Ih z<5H6Ix%-1spLTMa{q=e*chl%x{S}AR&XB42Sd8S{u zuFUB{Vuu}c-6Gm3e{MNur?p9Ux1oImWilRBSy8svtJkp-q02)31=4Mny6caqh+TJ3 zQ^#3h&AaHjlj%B@6R%d=_BVQ(s-|s~U30pyv|@#m%hvGPd0ywU+ii|M=-TJ2O=;M( z^>37HGyP*bohzGRZTDfI{=9qImh#FwKZF#%$TQX}c_Sh9{$p_E_~l9GCl2njX0(S~ zQozum^K!24`r6^qtDMBW-J)d&EI-;*d~a=<(E`P4)f1x*9hsX`u%iF`&##*m=Eogh zzB8-n^v3N^b~_awl;52j+%4JYkl?Feg=MGf9p-qnYUox}yX{@c(=zi*M&ku7G)~Vx zvhtCg)6vth-Or!9zhue8G459-^GYALm3yISZRDbBsATg-OuuB9^VYJ6QTgMn#~Snt zn>sx-tNjK4$VG?>h5O)h|8kO^&=8 zWb)$8`X2rokpeL@>lV5ZsjC9I9qnmsemC@NkaX(8L96f1%G%bYYkm~d zBp&X1Qf-yfn{4eiZ--ki*U0Qvc%n&h!UyHVp}xxoD!!hr;jR*}zPWFgxf^^>ZS;Dh zw)5HgZT-*eYSB3|G9uCG%#qQ?DJCTX1DUe}H!3}Uxl}yz>|?1xkrxu1m<1|4zK~!m zF*d;bN|WN$kFskLuAOuWYyRny>_o$wTNUzi^-Fe~a<5rN=Sr#&vTnKzI6Cll{j1D@JPi$Y2uyz4zHhGk{ma%c^BWjJO%M*bEJ!#+)BE5 zzv7v?@q!fXeycxy@->~Gf8_mJIoF&glk9Grde7TZH#E~nH)3|F-sXOxi~1Qh5p*81 z+eu-l?#jufspkd7vEp}AcE0R?HhXmaxwU=|7{_X@!`n7krMuml9lK&n>qvvw`)?e+ zXg{ua?6K>cR-V;byH5RK@W*z&ruT1p=c-XrsiRb_w^ys6^~danCW*K6mo>CGXmz8| z-n2uTu57=z=lwjoFJzLly<|;S2~y@>%h=btI4OSWbtnA~^4pUWy&SrCti3&}$d4Vi zS~h3=g`EeyPlr@074$F5&Jpa%IZ&2<`r6>AJKmqHt$UPdxCSh9(!Ludzvw+t<=%T^q9Q|f*n_z6&3s(%`C=^y0S49?9%H^psvf^` zH^R|i;){ee@o$4v=GCbh?Q0mi_k7%ePrbeS$CZpb*VaV-@z9ti-qQM}8Qw=NN59p4 zF}UmQ#l-^26{e{OdS{{1McHsb4ti3!H^G#=xBueti$E)UX+93|&l#Z;+$ zaoVfhp^eumn;M<Gp$yaLdQp1K&mK%6{HvDCfDMWt#HXI~(?Qbu^A!5SY8^e3w-hjz8_RYst6{8n*_& z>>s`F(SsP*=xI%>#3rX$oR}bY^60c=AL|}x&M!MYHGNn2sO5*1SMDead---pi#+oq zIi-b@OgDV3x;^F5vDf3rce#4gLrHZ{+>+M*qt?$z3Gj~`J*m0ofK%@r5*PZlTx+@Q zO-RYTPG*Bo1$HZ5zyIx%xBc2$+M3K;`+9+z*@us6RW7-^*JTcTVEUqYpyb%WTi565 zFY&z~I4Ji}qOGl&Medx+P7%+Jb&~NpHGi&D`$zscE<0~;k1cb}ln$_dYra#d*kpdb zTeSD!rctAZj7U&8@4ekT&0g9fW_453nOC+hjqjveN_3XRwXBP z%o?z#nb_F2HU@3AoKLPE-_~t;z~!R{d!!Ew@K!F|oSxZiVzkrIvQ1u@wiw|9O zvi0X#a=yxsHqWw}tK2_l!86kbEzTu|dMz~Aw{^qy1x?O2pI@pq%EzF4(+*osj<&W8 zK0Ncnv?eQ0oEdM_?|6$g&mJEgJJof?5Y0XtJ9UegKK(==`GuXnHl%%#7tC$l#(kmt zW{+#$krMgZl5TC!&QNXA`q46Vsl&>Va$}z@7~8#1g{#l=;ri+&ahJ?r%v;dxi^IuxJg8+{2i3HTI}|@9w%&fIX>Hu`#9nLoi~E0W(NtTX5lv-jYLW zhhz;N-(vXWO>2sp>u43ueg8qD__*zo;h~D1Mt?pKvOVu&x|~)`;M2vbs&f(~Tx=2| z-A0OCxvHp9IkTnW)u^0H=~pirFBtFswUzqlg+ujTRBlw(c$=Lwttg_SV|kjk?bz7< zS7*syD!G(+<&=%##xm1I9cs@sZTB*xw0Zm89hRk+s~1h(y}vT@ZS#x?hjdN0oStrR zH0AO=tHWcuhPVGvCaCl|o-e{)z4E#R4ZEx?(aVXHxpxvGh_Vj zIXs&Xl`1}AU64wLy^_4rjvt$eT$M+wyg}6xuj-cS zuWiR%NHg+lGx+KJu#+3sbkpzE`=FLy&7H8LdnDg4xFyqb|BPWZUls0~S~Zs#CqLOR zRXx(7yY9$(cLUYl!@^tHuJG^RzjC_V(qYnfBo^c!a%;0yTm0sW4qf9|! zO!MMqU8f!NtrY7Vvn{cI{II?sPj9R>KF zUS-_$orbOT9p?6HwXyjPS%pr;&1M`k)SsCVyL)N&=s|6gwKH`FxmFDH^AbzAeA7Qz zHK2<_gwEv}!M%Am!kUzywArg>;$<*t@HGN(X-Traw&YGGp(tBRgJYTW=ZpR)m!*A|!oZ52v0_Wg@i8E&} zemk?B-|~*56E-{^IXwPKR?Kthv)wK%3L7G4p&i${_ZInERZ1rwy&Tbe!%?~YyIWb9 z*;E=%%rM+6xkxKcv*z5_AvYE*ak|sy^f}Yqimm55d^T%pQKsJMq+rVU^yWKTHb_Oh zypgs}Sz@_jt)GU;o;SC}Zgn&q-J-;9iq^E42XUQrdYLsC-`L`2AAXY24CgsZe=PH7B z{k_dwwsRf*_4TL^hZea^acNNabW6>D#r8e7lKML|d-kNpUaD}|f;ION`g&T99rdx| zgM7t>4sjo?+NCOGOd0K~HEOhY;G|1_>Hf}T4;Ht#4O!Q!dGeq|;nJsyQ#ZY7t2|}d z?Rjdlu5A?upIh`K#=vz}_epc6Hr*bUxIQvoQ>=sj{HIwj7OXi`oB4dlpmnQsqc@j$ znr_;0vg)JbsS&$d>31=>vSq{@EWaxvyP!d zv*NpKI<;%f?8u;CkGB18E)M8uE8Tx?&eP3~_QM_Yk~e(36dA6iJG}PEO|@a{AlLZ{ z$!|By_V!mhvSY^B3DqfUrvwfh-qG7XLE3)eS3UV{iv2g`kGAUD?n~yKjmdXxq$7J= z8s*>dQlH72b0r6MDLt(+>R_|#K5vf%cxylH`Xpal+-3IcV^3S{@))#4&@EbRf$2&4 zs|Q-I?p`oPuTUd;t>fU6v3BdW)#>&A9%x;3xJy_HxpMWNr5MM|Dq z7WWFWnbFf|QBK>tz7dPNyXajXvc@t0(aVvw1;eEdrS^|Zk^`>D@FfBC%z?Pqlv(tqmZ=h0&>4jezWWx$S)3hIs%f{G); zyp#vD?CED4|2TTpZn20Sha%&uj|?0d^HEOkOs^30VDqqWyACmj4Z8=}3c4PYx?+); zYEwC1F{2>H{E5^R-%cN0TkaV(RXa0wS#ax_&Lf^3m36z&(yP!>)$z``1E~YYzuqa= zd!$0$+dDhnxR!3V64TxCIH+Tk?ECZ9U;KM4nG$(LU@LpF<9frEgN8&`ZoWDc=C|p! zZ!@VVK1y?Ui-T?VDKwpxle;-bP4YlQvvtJ{wJHL&+ovDxUEldiYrjbm^A68{GBLn2 z#z$#+eHXVDrM(#$tI-ppEhR8K*#Op$~Lz=X}6+M^R>mft|iA78ytT2 za$?(=c1L0?V)j`|y*M!@u#45&O<9-Cb!F!_F=^(mly$gs@F-2~RMkbBW4afg^o?Jp zK4{9O#da~ed!6F*@@~D&yAt?FZ$X9q<$+wx*yWi;Al*P(M4b>eEbUvV@a=Bcs zu;IbnGoj1uw1c%fSbh02v+8Kiz&$=IWQxb`8(jNDF)zR+QtY{^y{4AZoE_%AUfuh( zu#M}wRHj!-hGSNeg~Eu~sP2Um`}UbS-drZ;!&wEf)&{RvzSEA4h_Ea28TBx`T`e;- zbWAt%Ei*QcODNClzid>$tfabm5(_GI`gYkqvS1`@6PQvy$bHS}tM`1op4e-uShx5R zpsjrW@-^dOO+4mGWmnHVn*Qw5(31G&!=5KUep+>?$NlV4k7Y`B@6ydK9Aa-P?mzmy zhHc-f0Wv2$JE#R+HIwahe9s$|e&We}rjP5ly411#=al4b@k+A-t9yz2w4C3>{<3V5 zV@+jMf2re6nX6}%S4Hc0xV~WBsIf0UJ+L#sxpd_Il4_-`(=$5wH?uI)TJ!beik+1` zo|Lwn7UnQTE79%XqqP_JPCD3s`}7lUa*nDF*O<0&q@#bpF>b`O-ri6-N$9Gg!D}nZ{`I2%8xBB*Q6#`jtBO z6<-6suFGDZ5S#h-5@G2>J9t$+zBPYMFXeU_$FeUsQ9NYoS61jV=wsWN(F3K@$}~sZ z-nVDFQS;GDyX@+=Fg8fiVau}4-A`!xnmAPzOlT$O#Y$ZWOu1Lw@NPs^M(81*Av4z$ zcx-m@kLlD-Y*lsi{IlkMnM>+=7x+$2tSDQ4t+&D0aJfyjs^0N2??1}jm}DxLzH4l3 zs~KABw4T*fbPgWeQ>UzE+OrRf-fcebBOci)_4McU3H__OOAeU1r|*DQb+7v?Jtz!| z7@=&ZW~(32TW#86*Xn#%Yn|;XvFV|Ym2;2!Pqam7k*xWr9(@UDPm~q~9^3=PFZ}&LVG=9s_9+m?y z-HU#ZRp>dnV(Hr#vpg*>cP{a*Z!%%L%YspRyPHVH9QU*6_M(egK<{yv-{eos%;PC%TRBSe`52KKRD|W}Ebz&FwwD=ZP<-{@eXRk}8#hlDn^u zyWr8kdAQitk8AcuzxkBpH7saW>Dl`>^JfgRFr749b6~WZYlobNZEI^Rr|%nZ<>+pK zzyBm}<-z+Gu30s7`ec)K#b00VUb~i2>8umiRV+!r>5(CBex6TUFRz~55G(sI*S|K@ z+)?^vpP?fq?=ACw{;}TrV(i6(ugqhz`ZZJ9z2LF*lYmhwc46mI_nkmrLuJ0o zy>%_k4&UrLr;F0S=;skP7i?KrkYu58CC;O}PI9H1?WLSUk!8ApVHYZzt?1P|#Yo0z zw&&ux&!#?eX!1sCg3IP+xh1Ca-=5q3ReEo;js}ij`%Ptfuh()oH00(~WA!6zKKUMS z?3Au0ue_)A=T@C3W+@rgrOGMl`E|}tSameuwg2m#AA5g!v&?IhUAq|rbCqM8jIa-T zrZ7I|p{w;n6{kfpkKa!$jC_7J&S%9?cH6`Zy9{mbn-Xp_?aOK{);)ZGV?&Oax8SDo zu^H!9mCgL>ShdXezEAEd!}DoLj)jxoMaKF@cRf*CCD!50oU*EE@du_7?G5m>z}4` zMD?}Y)W?FcXZrQ3342(oCGWf9shc-*ueoxqZ^$|82nRbg>msWv{lMLd1MRM+rv_CU z)ZI$aE{ctqKd4`b`?!VT&H9LKoAVC11XoqfzrPH&*vbnV|LE5djrx_P1?(6C46Ysoi}5>0n(atXe)$5W}<7W+ZC|Fj8TcMJ6y@}_MQzmEYtUUh+UQpCC{)KborE%w{ zTDnj0jce{WLFdCTWtS^zi$`r8l&d#j!I$~IiGg1CGyE$zZM)ENqkWUOoAS@coT}^9 zWBrBr*9#B7G)$gmRp@g~x3BEoA*I*f&N(-A>K1v=%SmkHr2|^i^0Xk0H^N)x%R!a; z_BBHyX#m=oyf(>uGxszm?3ZM|v?UIoZpJ8ryxucptEMV&HMzn^scMzTbB(mfTc zv_;P~``x=VOsri&@JgA06`49`IvRcL-ah2^6Tc>g+9SM=`dgGl-^>`gGjHatXN&86 zUq;+hU$o=Nh|m5Nxdks;NXUM6y%^PWT|uYX{TIBd>q70%& z@ApMJEwsm!iVYvjqpw9Ri&{JQ!G=W=S9-RsR#y%c-)y|$t7$>>c|XbbYqrY1HXA)~ z`Ih~a;@X?5il(=HS~}DwFJQ>)N6SK$f`hd#c0M_L{jzmBZTIC%X$^0&Tf-}KnAQNv z**ol_YB!F(YT|rwW#8Ecs}`3$Nst?_8oXb}>dfL1XGd8#{z_`Ve?;Q zKdM}CRiRb>@VFMXnTMh(4(hsnEN|&lbU9vK?Bwj~X1A}8jTOXrLB5CTU0kJWq`x(CVp8t68i$Idd^nbI?MOsks-BLTXgBvclnCNGfqrv zQF+65*7|Kfey4pWg(t5|6(6aajW1f#Sz%zO zfS@67Wy?lC^@^`*=6S)j{Pme;s)g5^#6(}2)qAn@x%W3@{fsULFKwQ7;{CYPLb=3s z5Bq-H*VWcCKJ?C4%Pj1v6=trG#A~S+r;-eH!SgtD!x6oheV8oSv1>WxJPR0q`K@!a-P?$c9g`_ zJ(^jchdkHeb{osV4;Q~0@O)Hri=&Hd?>}vQYyEVe_m8JJJIBRraxy$OamVtjaC+6< zvxlGMjuYQ_GHBqyiSt%ls2$yEuyexQ^F#ETD7Kza$*@5uq^E3srkyeUc(1y5fvj%0 zsg6y^^~;JeHEp$CZ9N{fet#QwyK>vKWlmG)x6DuM(K_6}YIk=pwC=-tGnhXZFwAK&_^`?AToSuIC| z6faV_cX@8Hc!=5Z#kYnqAKPcleQwaRZ*q=DFMDI}=_jHsF4X9p*G>5pdo6IfPId9d z<+FA#lho;&p;e<}P;9n%$u)ldK$Vn>>+`n)oa-Mq97l$~!Td zqn#D%e46&vlYe}sZ~mmdc{95{Nvkbb-T(8(OBRzhycHigGXHq@Hyb`D3Np9%i2C@j zcG-uDa3*I&YZdd!&7aTeb8V>4&ES4w=UkT@ds%d{)#SRFZ)?Tg4>l5O7d`3Cs|^w1 zP2yv_YYx4wJ!Rpc)UW4U6$Q(EA~elk9KEB`vFeDCgRJ-!ul1A9JvW(pdRRyCm9pC+ z;zx{p)77t5;>~RR;;e4-kM+3B`1QT$ELp7EHE#S$|CJG%PA_-K9L{~vruD&T?+4lS zPW5?UHKBO!{1?Y8Vq4uknzQfWoI>r#tG_spk?wHnj;zfl|G<@IeIj2zUUXevJ!aX_ z;G$bQ)VG-Tn$i<6djRZUMjWUl)}`R?<4#RHxv=JhE(;~D&Rqrr?p z*={<^at_!8#NC}|e=k0(=6%E}$9vvo?MLZ(Y%CM6`Xp_qDEIPC>BRSbq25cS?=CwN zR1h_Ew4=(%s}jS_EzLiiJhOhxa!;rG!)_eB61%7R~6o=is)owmzr;Ad!vi}nC27qo>qx}b=-Ww!i)o%bHomGI;|AH zDBW1n&3opX$Ju*L(sjEP`-e*ST)!MYzguzF$X)l+wfo(xJh(3P_4cn#D{@Nn@5&s` z^wWOkd!m;|aQh*#18zmP&bio*-FVpB?2=1MQ#-SQfMnTeVh3iZJkwaO>r?ErIyOq3 z%|DZ}FxXyP#V-2wX{Tcf0R_oJgZo{{Q6F+vVzXDPgWk6HBQhS(epC`wk=Ly4sKrhd zfg?N11l+NF$A+s6ng8)#t^2?!z1KW!>Az~uv((+tyl!?j0Iz-8Qe~HbsR#pC)u}F!)+OPF+0WaNo0ORh2%rom=<6I$Epm zc8wP+7yHGEH{{lQU9x;w;`51XZ}y+u{*Bh%dFfp)%xE&_)8x(LEPTFxSS-H&eSQ3( z^WD~;w^Ug>k`6h#d}HG#jKV zLkByX#83M_08Bu$zbU6Ys<3ETjyZm!`-p}i_3688(3E}Lqi-V%zA7_#kl#%I5LW;u zf3Mw-FdV4ZmahE@yh}6dt#1zJ%n`21#|7d;QabkjU1 zA3n@vc6$x~<{vRb!C|Waq3B<4Sb{>p0-j|Ny7zjlUg zonzjE5UEFpr+oaE$*JF{gz+ZdT7efFd1ms$a`PNb0F@cP*qEby*xwXiFtb| z8}y*`CNr*f)>-)aGrzm4Jsi<}w1C3kwau-W=K&U-LnehbG8@P#k2iF-qZk^H#fDeXcrNA=jMS>-sL&0 zX%93POT;bZjCoNzl1?5)y*x?SK$iqi9r*AZQGb$qVDDmp$hIv_j?41C1Lb_CU}rM(pqgsP=y@@65= zJ>{qLt+0uj#+`%@9#PY&yVr#_5e+Xofq@FvKy!s>-b^%vrM#v_5h*0WfCX1h<<8Fr zw3~5eBBGu=5H!tW5gN6!_F#4+?Fc8eS^)Qqso%1_=Iy{oiPlfdl_-0uLa>0|htCsi zfF%&AoITVi`#H8k^3sx*vi&)>Qv(c{*5=HoLMcq&>4Zy5yK`K}vS)e6#x^0fig5}b zgFPemao&pkhXa%6=9;`AO7;;2M~pg%!P~2&TKOwe8t2l}**Pt4=T1cqUbdHYr?C3u z4Makm0eZ2Ku&lJh3SYMlWH>Ikwd+N7!auHJ$Q0$I73?dGTO`Kb0ni&?s!t7oX=mAv z;^nDWJigDRQFTCwaTer2fZdk>Sw^(bw`l}a5iRYqb`fM z%5VAw3p7+KfdPm!0)lVdXSZ1+>9wgCM+)~1Fpjr}uJnw8Xc_(-(ZXA0aEudtUs_B)mpN1zgdZsz ze-8={P%B(58XOgT3>lSUFnSP8kCo&_&+xaZ(DVAPGp7+|-bJ_os^A>oVFvxqstL|? zI(7?cPMfi#{})w31Sa^2`LvmS_&J}$chZ<{EoE`e*dSFn>})@}`$Srb0NPCgS&3nU#c_hhksMP@|DU_ZNiXq(SLKGI6&ae#XNTf;GOL)V zPmw|xoMgJ~QI)QF&ZxLP*?7(`%=3`rnz%k=%CBrMxfdWNV*$}?EoC8wawiu0qfjSQ z`p|k*xMi@oQVikOmQZJ716L37 z++)zs4-~DmjJ0G#K`Y3e-^K}4$hN}EC8r8{j=aKSOyfY!iXyZ zcX?alQ0Bvh02$3mA#mM_T9RgfZiU?JU?WO7=*^R86c0c<=Vn?XuRDW{u*=b6q5GPe zQVq=pTSq4F4!?T}845Yq2C&<4VMn9pqx=bh{2uW=+kEmB?FFj<^RE=Ag0|jaB4%1m zA=8x>E5%19myvsgNKqqkbO6XZNTqsv#TJ>Yd37%FHTt%s$T8(9&?MUqtGQc|iq;O- z2;fZxiG2s!4gJ@z zQR#NnNA8kOHuMHpMIuG#r}8`~C$FCuSBu)jv)X=%KXwksSzJ@sMDzU$7ET2m1RsOJ zJWq~kaPs+*DL+TFOJL}u7fwZ^&eIfd`3H4hcfEyU{#X+FWP8Obz%oyU$t(srTnjHW z02ztdzJ+y8I>>lVHM7sU8)65(U(wyG3&?($ya;`dE3h`Yy{#xVKWf;oq4XEvFqF4<9cA`iw` z0pv>>WeY8#&A9`uMI@XRfmVO=Xj*q=grvR&IP@YC(lNPWF}Ej&r%_92MB5^O(|1Iy zJD2bTdxE9|lkGXu*!tJ7QB%*u!yt#F^8m9P5#Y@+z$sM9Lv4q^aDO(toFTMZ@fyP) zLqBKG6R(;E-49clr!P6>*yn*M=lu&!y|8$qzu-S414R)-YYosUS{#|u;DE@7Yx&UT z17k3<*Md!oj`Zla82`ixjvh3nl1>wNI4MOg_mngKS_dm4N*W30G@5&-O?|y219(jh z0O|2yZuQgC4>+7JQ5&!ewQ=trGQkdnJ)dg|JqJKI27x871{FNgh*k>1C?SS*Kg*g^ zk9H9)b4z(53;=lz9bo#Z^tP2-jzZj1fo=R4(;!{r@k?Pgjq>J&!vY!Z7v{z*p2T8g zS19)!;=?~@l>`dchuR3626$t9xv!B5R{95E(xtd$PTSWr|j=;=nSm^>}nNgw$+>O~dG>J*yk{|t7C@{Kly@}^i8p#4M*qH9vkI#fcFD%L?BZkvL*GymlO6% zMNe+8Io&AVA3!e|K@)#mv^wjELx*I|Vi?!Q7hi$1+2+n~^V8332$>26_eC@1uvbNv z>aPU$b4MTt8-ttNCg!Z_t@+d-h*x|zF$N&HJz~IW;V2$}!}|$@L63!g4KtJd5l8{j z=>gzG==&z*oqQ=P)J+MOs;MBZQt?zY;(CISm_?(<3cttKZ+d-oEvEJf-@tIP4Zgk4 z(|>;6+$b|W$tZ@M`*oStNp%=$OP>`2%Ef3iNbX<2c=Ws$fS_sSErTJEGoNmK0MQ1F zm;{e!9PwEE@vOiIC5a#MCxqd)Z!lCB2Ph2VP_Bh{=D>w0PYS_BR&p;lDHyoWl4o{YVww`G57sfwe|~1>1yB`*UU3;eY?S(pj>OPj~i2pG4kFMnsfVOdtA$zKs=e{ z8TIjvYZ=tnoZ5`Rk*xYO7QEnQpefLq?Q|7HbyYe?yk&Q#^`9v|84bNxAiMw#W5zDb zjx^9P^KL`Q4{|alI-)|}q1E(IbRA=rv>AI`Ud*QmQz;$F%`(venF!%xj?765vjJ$P zEgDt4?*t7%>(cai&K5a$ZpxWeg#wh8t%{7oeR0fMyb(_Y=eq(o6uz%iKq37;U31fP zPgpJIQ2IW+TlX%U-@X`y$#Xs()GrU>+xOcc*cm>w2La>aK4`oGt>g0tR|}(Qu&t z7@SiP(-IzR-Wm&FUhK+fB9g2-=auGYyweZ!ypzCPE?AK*;sa*ca5k5zAVoVp-qAn^ zSJoREOCadHodGrnYJ1v~h)wDtufAm2L1o}By*{%BTv)ueXEuLM7dxWv( zOlYn;)S!MXOKo1_O*EyOl164dT9l6E8Xl2Ad$ekgG0rJ(I=gB9`fO#hzVaB~{(J{i zPGImrY0%vm>dD1l(LN0Tx|2bnC;y4M#>|`Hcr(c#h0#-`m7|WLWe%HYOyyw(U|E?A z#n)<||3oW>p^2NVLv`OKO63j^+r`>3E*5+^-Hv4)_BfC1%6fz>r#Jmd1skUX5~S!n z1}>?BwlXS?!b@#1YOK5b6uE@?cFIYSBIY4H4W1~U&`((ohVFh*{m z=}XW#q@UPb+9kf6d4*a`$`&x*o0bLeDG!1aZ%Ql;v>v^7)X)$6M!!X_jG%UlU?UN?e z7wPGlr-tE;6WoX%OL|fcl$V)=>;$&K9o{0_Br$4?IbZfyHb6O?F$gpA0sD*x2V+<5 zWXT52T#CF1>4dU}#K>tHeBAE4c|?hfT&5!L8=|o1-wo9W4306M+Z;9KEdry#U3$Un zF7kDsSCw@i_zTl{Xg1Z@w0+T47^|D10!GR$<8pn#}t(LnU3Btl)@4uRVS&_nUL3gS zz;v$CcogcM>yqEKzjLp^6#Zr#1n3NcSsBQ@<516UE4rC^+F=*~RW#~HBNUGm`BW!x z9hQ@qs+mlA`t+%gi4evLx4l-caVe8U!!7`RfFK>lQ@+v#wS_1vN3qooh$VPY6t{$} z~75p&`0>sQQsyos71ws82~Dk8aKCP7CP4`+GE>BZ^4@A6Npw zyD&f3UH|~c+KViXdcYuB$s1ECTevW6@njl38}^#PdLZmp^P8BLXEmlAS>3hF270a~ zs;=-k69PsnEGQ}T1+hLt;d5oT<_ZYJf+j^g_DG5JEt2h>RE>_YNT0zsxTNJV&*4Ny z2B?bhJ$CoUD7TDbz|1>4k3p=etC;Qjf*&;6tXc46SLieSM;P#_suMm2}#=A2u2 z-5WH?pWy-W$1~2b#IFlGCvjINm#RbdR602c0(_Yd!FK@!`|Z@7bs^-twmkXYGsOM& z?N#>zSaB=>P?VcYdme;h(9o?&Wd_bUU09kDJZl)IO_!Gv~mg&b5!^jCm%_YD&p3k0YnS5#PqI{IhQN(;C`u z)}LFEbDTa`H!c}J+B`azRW^nI}PStfTBd$m=QshnO>ffRy)f zH9@b8a-~o_+l863BnjSmzE&b4dN~3Q*D|$L=eZ1Q8Auf$ji%0vV{3|Z8VVaEXGMy6 zTL?qc&$So47XYicq99#+y$RC9Z|X6Kd=nWrHQV)o$m_JTj1Zdf@x?sE-iH0p$w!%( z`yoIpQmH{)+u)78tKh@ygAa#pa6+3SW>&0ImQn^8H}<%lALz~FT=PC6uhXmI{ozk( z4WGQ3SqV4#03IU(R-+aNu;k!~$)Rkay+q3Q2XY2j1xHV*7ETu~2(I3_#KmHy zj1A8HPM-^uyg{pTj`rO2vIvD2cOu8}kJUwZ1S&nh8Ri7+S-hhT4 z<(#W}#Q{z60s^2Vqo^^+sOH=>3kCJgRh)Ymqci;ps%c^Z zr*?m0bi0JbaU=XJ7LC2%BVG2r>J7zWl?CbMw9V%OqGt(N_Yj;eOeGq9#f-kPz2c#u ztpByoM;*B>(bEd>2!P(j=bd{7G-8z2q7pAE#~hE5^@+7-fgQIfLuhZ&gl(L=*w{L0Xk}>X z@pT)n`QRvmENMk(Q8?6AU)~3D+JNauMv;6&W7BA@N)rfF&~KjUO+05=}5eVlss~&{amZzf-pmzgIog|j}Zn8JPPDwVR6#_?u`;tY|dS%|KNp*!V5IY_5?=tMnc^d6p0XKFdYyO7a6Dq_@8iMvi=QV^x&5di5R zt~Ttw9-qI@CgZU_=ir=n-)G*uYl=quP-S)=o8`e-jEIdoE+|X;AAqhxHzTT;6&YQFiTCA7<_B0cF4eeNjamh!4z)p$U=Bs-#`zuh-K(Ls-w*T@VJ*CPlphC8dpY%mZ;_{MS zgnRRBw@aSFQN_*UD&!llEUE_ccb?iZj4*zAuWjYkeeyo#H_M~O*5E-wv1(w!O+2PO zLDL(y(ou{a&{Z>kZ`U2kgX9&*Or2H7piWrAz|>~sr5%0teFIXHsM*rN1sFz>C*;AE zEWD!5qB(o{H}jEKv=_Y?g!(w-$+KTvs`-8=001BWNkl8w$sVuugfVvMc~oHM$_2E6`}SOo~~N1lzId#0VhG#1jP zGRqI#_JS*MJ}OSg1*`s$yBUENy+c`!MBG4eF&;o)u^-|5*g+d@z_GPGC_Pu?u@g>n z?~FZ^pJ&RwXyr6>BC~kN>(T(PP6kH2gZj3KIVSIc|Ztb~G<$C6HUO`6VmVX=z z-~0~c9dBN~*MT}9ocT~DMwlvi1JWV3TDQh_?xJhmphX!dg4rP75dV&mIZ9;pi4Ov> z%|{A$*U1%f8@yWK{`pQZ9Xr=5*Wshnsuqr!)?u+_5j$CercU}Ok`U!*+KXNcl3-?x zI?Pz1xC%gKdx2}ey1VN!&|%>OG*Hz7GGAv{M}eRC%pqKcmp2AmUzsD5I&L+F?}MMK zr`XxrCiY^*ROKC(t6@Hzx=cU_RlIWW42Jp~#)6y-#TG$x`s7@1Dr zGz zriKlA>oTLp#3=7U%(Hof3QdDMozzI?s9uE9hogS?UNKCg86~PiT`Y>nn^yGQJpd!4 z1Avv{fcxuHK25yiPsfy)1nz>FZ7(&s7P4~}n!E_%--F0%`gRWakri~G+Ka~2CxPYU z@Q&IV=7jbeIY=x!bZ%_Hv?IlkH+6oLW zEd{DkG^GBk9COv0glUJuK2y= zM_NN&GrV2~&li4(KZo9$ddM{%K&)zE@A)Z=h+`Un2z&|kbUre*FtU#sB6La99=>wbA1SQpX`lmuaL$ zL5@MTk$l2w8nW!i*|(G<4UtBdp)tMJwZZg^&APR+cmz?8v#&oo0h zIpsNCqLmu3z=Oif?Zci+u{In`y<`A$8muF5K@F0VQoRv~(k)_kXC%3dNY6=Tgvx(L zuUH#7eM!Tmpv`;O2*Lz~Hb`*(juJR$1iz-vighzL)-phd!d$L&yh zEawRBH|z$g%a0424_t}U+H+|cg8CM0Z7Ir3VocS5b!ex;SCBRAUQ3Z+L~zJdA+}t9 zNsqb0{!CkD{7#x3|FP;Xg`IOQ;^9B-~}%R2`Qc* zl;^Md$sB5~bB1{Mi172A{_#IOafEi#1sHf(hyJr9guumfM<1BaIaa;p;-?>YCiEJq z9GS1tX!tS*9ymZSjvmW!(7-Z&Zo_qiaSP2ozN|I=tWejy?wf~CInCj*APWggCmr@z z7>SfpQX}mV&hPw{n}}v&hhx?Vfm+$$;RXaIo2N6~6zKDuaR)S=uoJBD+Wh9cY;I%+ zDQRIGlm?{QgcE_h_slcUx5)cL2avEdb?QmllDif)j-K(4{T%l0K>vv z?j4Ubf}|Sea#|MCVpG#@!2s~S1Eo(HMjAccP2OcVr_E-bRB9QK!_=DI=~!f%ob#fy z^AIENV^nEeGSn5<%u;tM?lIfCDRV1~MjFZ#c&|$^Ou7T;0mRA=Gg~C$hd=0Gq z=Bg1Ky*(-ZRNR(=z`JWF`YMLy6g2aYQDrIh)qjDS zdetY+8BEuk8IYGi=ul6e1ulJYSv46rO{*0>H0GwJbH+#erY!ZJA=qju4-HWUjbp;c zaU4}KfMN7idJ(ir*3#`>$8~z0TF`kotWvdZ@{UxIr{G?oh%D4?fDOdrPq&w(00k&X z3J)fUq%zY}`k|CE%vKQ(Fe-=T6MZiRmjQ)TN)~MZ+#WEwPF~^pk##vbXk0T{P^DPD-A{20*YJbWQr#yg}t@SFZa8Qfs_9e#>3GdgPt6Y7sKA6=47 z+H^wUS>!69aJ3Z0o|H3d-;$eki^hIS5V15X>i7D^%+vQbGNl zQDEp=H66+B@Xs=j9G&1dbW7P!&*c=X#pk(-3AqiNeOv^|b}H#W2h@-0Z`6VN84R9H zL5%JJxPwkuI2fwyr(=ZWqQ+V}Zs~#`F`K~Y$c5?I+88t*pOHfY9WR^>iGHrVVkh8O z2S9NGI842+0)=6rf(FVo0kHm!hVFoZ%WLKJdY-QwcX0l6Mo!X$vBkE0~a(WW! z%jBfRXqonYdK7s1y#gGw7}~anJ4!3iAs83YQgNB!RNl6wYDL{ctTW>4_BY}1&4hU1vqmVj(1YDka0MlpNOKt}~)=`tg zN7X(P8ig<{t1{xZ$EyGXFh8e9IWu>ef$BEn+#>=;AiAN)0!vhdJCrqn=sN@!qnMrR zU8+fpH{xWRtDo7n9&i!qNplve42Elt6$j0;yFP9y5}1R7C5Wh1ta_j-`%7{REul=} z)SINlxYaQ_{?ASeaP4M>j>4RaLCSUEXpRoFCd!I19C8y1!ClX3b!~@}RrIpuhv8U; zweSBJqo(d^Av&U#8U&u(&?8K6^fyXyXyA=i?Xe|ydHP}`1i(dXP>0IVpRiODL3o<4 zZ!g#itjdOr>DY&hJ~jwTqYI5|rXm3oK_fh#^QVI&EZRW#ewM%FX5SL*glsju(qbXU zT_N{A|3o6;LF_pOsG1u@`j{B=Kq7raNExc~39&OSqXv5f&&$Bj*q{_jJ;PgZ*tfnb z_Ds+jE(vs;I^EJQ@hECH>EeP;x<$s+OKZ)?2={GphrqpPLLK3jErWkK_g{vYqMp9y zz5=a=L~&BVOS|t?%nmvikCMhV7^jEVSxo)tAjtQJqGn;dh&lIZvv+&h>%k+O-oFst z4th>245kg)Oz09oM*Sa z9A|OAStx+Xbq~zl!J!kH#~{51@Z)W6b2JDe-Cyt@tVn7=r>9u+ zx7#r7PmIq&Za62Nnv8}5pcT11F%&NCxMAh66Z-(-gSc*zfDwgqhBE|21mPz2qps(l;P`Z9JQUEODSHuVi*%vB0 z|2flPm>+MhiM*}7p3M$$3}BwbqiX7P={4K*Fk#V+kR5r66>9TyON#s8@SE4`BKPT-ZEfok5; zt-8jEe$Y{bgz3DR#Vsh>NX>~F_mSs(;Q2c$Ghe}7t@Gn`Wwr9_+iPA7GR9zn=FQZ6 zo*P}6QX&iP{Gg5$?xVL{At8<9oEk!`jjH$<@2qpbV}{iO_Sj6G$8a$YnSUJ;9?{q{ ziYje^I9Jk>$WdXB#m}QLV%dO&BBFeo4l>>bM#&uiuQlwD$8-G zf#wEidj2Y}M{9+rEt`|O3(YI~Z?f@3cftR7H^T#f4Nb{M&hao~qnR$sC;r%rNEJZ? zEkV$7RMdyi*^p|X8JZaeXO_VEn1A zTv_RzWBRK+*NyXXMl7SVwl+W`sh-Um1RV}5F9Bre*uaW5hV0qdW#6Us{Db(k@a3JU zNXI7xV9dCUc>-`wv5zO-=|m{LAE925od>NP*WTggWXvXk1ik*1e7BVwj60MK+qe+) zW%2V!?Qo30hh;QFV2n%;VXPTKhJS6du`Ll?c=TjXpw~o1fuyCq8bvT-7qO{JaeZK+ zWz-3fJv?&pzr83yT{9Slu5~*t;ZHpEk^810T9*%A-D?h}e4>PVp|lYx z-0Dq$Aa54{MQ?o`Id(IA#q2;|ey~fQ=SA2kbWR_}B+a@7I8HyBXQz-WE;qTScjh^P zgAMKFcRELhK|>xiipaBceY?LZoVq~jNv^tZ#2lYNVC-B0DwhSmzP)G_;1DK5zxa}g zEUdY(rFB{=8Nb5_H;S~_WGT4_)*M|+ZL5B8IcQ3kh?9Ih05&gf1G)gHGd#Vg8NJZnKywO;I!_pFG3 ziw*~<)ywIBOpPBtfa;6u-6Pf;4HwNq<3yS&2e9ewesbU|+UM^E z1TZjsK9SuSVzL>YwN#Xzi51rqN?xn1AU)2=fV~amXSy`u`x*VZE9-bnz2!Ahg77$F zlu-Ah42rpL-bty(AbxdMJWJ1GPv5kpW&EA{%va%ZEPL{AZp}UCm#$mhtwqwkfL$_Z z78Sz~35Kn6iK=C06q?ux@us{7!ndwo;<4G#^lmmN7+Jt7n&`P_O!oTxF^cK@dEJ`L z^1o9mT{_f)shNz}{VW=_G6U+)dW;yI8FC8js#PjD{T4yTG!^^PqD(~8^h?M;U*A3r z09QV4B@Fx=iy(!XuQOvp$7rBU83Y5DW@PYWx^z=vv7(T94(Yip9dvGLnfjf@qdLNT zCE`mF+f!_njmDD-^&w?_TqZQIw9xMS7W@CTy`@p{jsitPB&TovlI}v%EbDhf!_bs8 zM0Wy-0WnUgIc4&Nx%{juEVy8e0Kj?Z$5L(eNm{wAsQP}^kB*fveFGYLkSNco?&9k2 zW{PQ1W=7rQvR?2)>ZzNs;Lz;q>gy`Sm^z#FW3gA`WBWOl{8lxYv+VD<;Vs~xk-lzi3P5#IqD)kI_@ zhUI7SmkOpe^wZ(2GOP;rgxRcYi=QV*n30=C6~H8e=vaYhU>+ZzuEMgE-88)UX~yey zp5$?cZX2~pT+s#r`1YHkMHz8`Ay|JPe|7|)RmZx=T7iy^yy)%Zetyq2(Yd*k-u$y z8L3TP;Jn^y(F>K*OG|&a>Vec(5FmBxP1i*Ks8?}?#^;YF7+iXEMY~Fyll>9c3HZ>4 zKgaIL0p|K8#Tkf5192tyC`j=u8oJ#T_Z@o)6qw<*Gcjt8io|lE$?iCb0*o7)7Oi2d zr~i_1@cTk~hLR8HvB80fV9WK^!y11=;PEyr7_E~kr=vTDCbX>CMacpswB>rabrq#f zn5`ljJ67sdSbUkZ%Mp^;S|3E|x@PSbl*Pml3Vx^p2LpqL;+u<#@tKd#x)o7nWldnY z@trnVlU8nU$|0eGq;=V;6SZ%0Q#4q4aN1ts@j3votlJtx1;Xh*a4b3QcFvI;n5-VU;HoMJ>D?X=1q zlR{A4lyZ8D_3(qGUc{W}p8)hVTAlONDnqDc=#D2qIqM{Xz+n_$q{?(col!?T8QRKc z+G}_n}LvWFpn6 z9hDmg=0jX}xj+{YL^DFc-Gker#GrdBL>_$lxL&A>Ax_av(*ix08hHW$mlhr0A{s3|Kmg)<0en38u8xCI$BBLq=l5nM zXK(CyMQs%`=7y{B{u3QZ@74`jOAeuL>EN&y5Hj*fy*S=Id|o)EaSj6O@gBE4$4smn zSHGsc=+$7RqiEp6e|j+^8KFRFkj^X4edU)jCY-E54_J%?!`u<%3jZz^_5yVWTCU03 z6q(plw~!h3ZUHbJ+qWaPE8cvYEA2WrG%?!6QaXi#XCo}Ih zB${o^EqNG1ZO%tzMkSsgIpESM1NO(I#YXR^9EqWues)Qh9-(yE8N@#nvyTL?#=s}T z5T<2?zN)Xw`r-oh^bxNiJ6#&3tWLAsJJG`i8^8)cHQsGtUe zpx2rOwZ+iwyT=aK&w~=EBKmp4&UQiQFnzo@LA~weC(P{Jo0iBOKOr^c{HQ+4hf{^> z08M~(ZrNO&SsTukPzWvB6^-EBrw}ws*%`J@3gc8osZ)VUikYW!Mos{X^Pfmc{j`dr zum_DEIFhPS%vh9^v)!JRn8iIU?{PQY7|K|p=1%^yTArfRcSTtY)0d2cIgou!y|;jy zQSzp-!+4^Pi^G%=L}+zHBC1<}p1g<-$5=1;c@d2Ew5N%@_G95sgeAvUiYsDO1JUB4o| zJ|TgT7_t1s^~nQ<`8CUO3)G6W8^Uem;3I}-HH10aU>U-fzmO2LnP}B6j&s?pS$Mrx zR|M2LL+mbe<@!H)Ti6j|33Qm0IMjC(G@--*$Iv;N>1R%Gq(b95@3T1j?pQ!Oog@EFI?CeOBUas(Hn82wDx%uc|9x5sUq=g41Ek*pD$I;EC} zlArP=>2PvuT8@A|=BmhPk>>zdH=+nzYCw-3VuMP~=@3buYcQ}k+u3i$F~+8aiGsU$ zlGevZ>2rgT4TLG#g#9F@Vw1~lDUA=lV10qN0W4~7IkF1Fn(Q$OuYS<(0$tCC6M|Dl zZJHGY@O*KQE~jfAMVAsY!_SwVcR1+5t@Z^<;kG<>+S$YY`-U)vPm*1vmFT{}G!QhR znp(;=5PABXBcE?C*a*~$i9qi=!L-6o3J4K`sWHrzEWHd4e(x9xA=q<$XKJb?yF$J; zyf?xl9Z`*^&Vt}2Z3_V+`sKV9z|Ht0L>BRM?ZT7HXlS!WBpOq)_7))k8PjO|qu>ga zY5ep5tGPV%($@&kqZm6&smQ}9RO6g`3!}GM&J{slL=&^ zHkfTD>i5#9_Xmz%-eaIwK+m;S$R&LHrncb%(DU1;%E0WBI!k2>1b1r02b0JEV>vD4 zts*6tTqhObk{`h3r1aYB;Td?y9=1BU$Y`%r%x@xe-+lIzj!COhp6e!$CUfHg)8yz3 zb_nw%Bu9r4qBGGD;*it#<>ji@^Bbq;leg)mrgmT83REL_AQhug#!b;FYQwpVAr9GL4 zxj>*vPc$9V`o_TshO>b9m>4(bCwkePC2{`f83y4EXfB}>i5X30c0aMy*i@>rA_G`` z13+J;Ewtl}j8>U*GQ?0y-d=uM^;OeKP~8>jEhn*o>AL{vjGpJjrkd2`ZtaD*Q4z~# zavf8e;ai9%bzCw#&OG(aL?+oxh31@mALNqpp=D5)`eUb{*^rgN7!~xgOXjh#{ggJw z9mtk^?~PqY-1M3D`5HiY;gh%oNS#2Eb$% zGlq+(&27n77`ThS+K#tPM0tBe00fx1w#i@R5AXwPM_&t7exRwjEYBWKg!Vjuh?EXf zFUF+{MMtv;;}!{;ftRB^3+5trG{&(0?q<+RFJ^7x8U zrru$exDS!Q*PO+W=p8SfdG7p{krw4LB|~{hca`2u;YC0~rn%gKc#XlFUyC{t@-{7+ z2Nqa{+_XuG5t^AaWVJvFDnQ!bSQHd*Ki+-E8q6|8OrbiWrV|E)@efT)KCv}hCLweJ zMV?EVy!du36n#PFSG5=14m{*!24?N6!W31MTbG1ri?kt18DiI8=!7IaiZO`5XqX;w zPBze(*_qQL!<9s1qH!7%001BWNklZJ}4nXtCQ&0idJ@jyh5eiPhjYhIkLzm(g>O%=;uR#*W3VRAworOC zAtR14GCvf355l$ai{>66Svph#8Ywbqsjw*_3g?)Fd%*yWz5YxPIp_HaMdE9rlP|%q zt6`t+M>6b;p?6pv(U^jkRbN;-TLN$$D-KW$mpHKiX#vjZJwksTSGeA>oEODP>V`Lh z1rPKjr0Hw^sR2+GU5*EiXwB;6_Y|O)ILambf^T+jk#X4G!Nq1ScMoNw zPc8MN#Wbdn7u9tY0EaO76u=;pM%D}Xd-Fhadw!`XVvvebC4@{m0P78tnM+|}$tcw$ z=OOKq(po_SYdzkY(~^S*n0kAQ$C1W>_t8((TOJG0(ixh55zN)8s@Sjp$jOPDa9K_Tm*jEo2hrcFiHMQ5yBXx@|n zRMj?i8G!VgA|T0N(2r!o=&uzS8Oc)cqd1Ve3 zxxX0fl?x5y@gof+eFa9MF-~}VRm56pWG@SzEhiyg>pHqSwqq0jORA%Y2=GOpY@fdw z2mndz`1XLQT#GllMtEky*x|N}@eX|Pld99M=mHFJr#JR-jz=YWb?hBj3;c5_(P(t8 zAOhH_uH%|xGHY#ADAKwcBZcN%su24g5OtI>yCUzr+lC_y$4}vk;TTsJydZ(tmk_Hs z-0lo;r|N~<({+V|%_Xl2&>`>7t>3d!pdFv2wK`l$gvDJU7h5nlen|k7vF(594XrL# zOq%gxQLf@C`4UP-u6a&6UJPJJd*Rd?Vj(0XORzd)T(kFpwFcd8F7;rpFcW@>(P#Vo zGT>-r$Jc4w+$U#ex_5p9`0~#4)$^;nVT=yib3#D^6?pCTZ|wz_Rwzsc)w>*q-R_Dz z)7l^<1weWmMk;w{VFfCPAn(C%J$OI>rLPFrm|0d%14Tj;on4Gg-H*HVC~K_c$GB6{ z=Ky*}0;@+278ps))FC2VnEO4v*l`DSP2)_psfwqlNA#pk%i*_I3~2lYE#t)^)zFy<#6gC}}hrzdiE&-nq_dPu3BBpME2uj0AJ_HQs9K2_0*=JT5O2HKhoX zr$2)T>a5V!lC_LMMpg98^gInKUMyqIfT#ca?!yQ|IwKPX2?VSKDC0rr39$z_=!Vo| z&4rVPFuZ-fw;<~v1Toe^iq3xyaQV{Xg)C!k4a`k64b;c5Og2yg*!H*pYXU(}hp-_v zldLFE=}sDjz0w3QioOzXPLlNjta}8;_%W$mMm1J3)gB-#pFK^<(m6l+o?!D`xe z#OS}lu$J7`^Vz<>z2aVgzD+NaY%iY->t#X53(bKsnwZX7CwH+7iJHo#2WU$MNo;9* zKph)TdtJE}Z)=zF4w+Ha8Y7}n$wm)ZrBlblg2lhgpEv6wh>_*AmIWOt$(zx-ttl&N zARTBb7DL@fP?8ZRdCDFVjFs$%ogd?EcM``)qc+#bl)cljhKQsT2c`jWN$GruPiu}OX8BF$>!CrxA z3$$&TAdAt{d|?pnVNGtQ6D9grWzm#|Wwl$IOJ%jp4>Au2`-9M}P_XOf2_~`Y-l^V5 zvmA{T5#O;nDwKFH@#0wQSRGZ6&eQg2 zT^?3gi4Z+_bK)D5vnQwbWL^-_b9_-7xpC+#_a5`AaB!Rhv%O^72+{0`w1;}Am4Hjr zVhZ`3XmR8Yjig_Hj(-i-8OM%kvT=2S52(Yc z#*t5p1Ib)dXQO$-ay^DjY)mQ#rsOWS$bHOGQkU2Gh^Cb`LzHfQxs}IIu+DAoHrvG> z!^gGt^ei>u31gvWmn`ayISSKK1v0-n*#tN1zNZ47vC-xv6bB5(Jk2we_9^Ba>4uu) zWMtUvznQXiALP37Hu_Cah*uELdXgW#KtK+;m%F$tJQamA#APaG{^KBGojo4A^3L{5 zv%V7(L+>6V-M}4oQx}0Fm$;+D^$j`a2>7AxyyXaXNXulbOr3Xh4BQUs!Yq%uzs)Cz zGu|QJL^FGo(+^3tqnFt-Ct9 z8`6i}?X%N8HJ%s5!or7$Go*p!)B7|#2^ttSFPF+%d@CvVh`J=#|>Bgp-t2zD?MPL6wVdbm=9?9^P%8g z$>DS>$O&WORt{Jik2#(Rds{;rFnIFfrX9ksR8ZlS#XW+x(hTyIc3Aw>8u(j6O_7WJ zE=Oine_p>9htGSA-IM*|b&V+~OKmIyampD7X!_`=fb|Ih|A+E;1r~XsC{j^TuV93* z_{pURzxu- z&t1sVtEB<4Do`_LkobE*i(~%da~Eu|sONx29hHb96Vrz6!8`d4z{kSzD?)3a&F@{$ zUJS_tBm)<@v@Tz4KDw!;`aSC@BmSX|Jr$}d1QX!a%eo(tz7OSc}$jqA`ul$CRwn3AfOx!?m)f` zo?$>C!k-*MizjQ_a*4Dm+Mc3Lau8YLIk#iEkf>(;a$7KVhQ@6qoGOV!q4d+`m&Wso z?%Yde>0mGs$Esj9Mg;Eg$=k=dp750Il1lc?LgHcVLc zAOkT;Y16h`30XM--vDM<3~VuPSp+6$64)?XRX!9-90SbzJC}Rqa*u2evn(HPgBRh> z$A0S{TMwCa5Bg+vX4y}gWP%YX3$FcItG+4usm0+EEAx8*bSH;;#&Y0rTDX#XyzLHl zWO-;BS#3GaW;(2dq?Gr7!qHmcx;t?SMOsD}!+~NxpJsI(Z_Lw+a!h1FE^-=?{`frH zAJV+>LqS=okm!+7*>VwVJwseWO(d1iuVc7hAzADgH?>%5;hDKoC|p~o_?28hmu!-8 zI^C@iqMU7%Wo$Rl2IySnJ70sh7JN4>sP2L}5?j%qYAPoy`%LmF(LnToi9BhTiFMAsyFWOw>gB&K#Zo$0UEE5g7 zHLNH*kdbQYFv|L0Hquy7uey;zvcTe_;4W}2e>qd! zDQ2CUPnMv#W2R50-_ItU{j~s1{^|JiDY%%+7)Sf|an37_mbQQ75gm;VhM!(X#Rw@VJTByWgg-9kw)xhdtO3XG5mm8fvk zxg=S?Ke)acCr$Y}M1S57ZdpGE=b$n63UHAIu=~VmaLygAr{-LE*cR2>A#$LmcC2o= zkMRd)a>1G-ZZFfv>6uKaqBbq6k({KpAzMgda$ql* zdFW}Si!%DdOL6)cqLmo??4ot0cg#@-n17`M21somnxfOIt(wkeXN*eg;iYQ`8W-}RgX zm&2{(*t?*`J;d+6y~o^U=O@|ykiXXXdm1oRJp?B-ieE=-g3a$;0f6|l|I}<{)Q@DG zN-K{{MDHP6q&Ga%Ae}oz?ir6Nl42x`SL8?CJH%ES&ZEnNj2)|x52l-TiP6SsjvG57 z?`Wj{VWK#b*q>iu*8gW8E)>l&NIDo7DeXAW!Pq(nl?Fy%0+>XCfXbSFpwJ4tab)oT z+U%Lm!8DxLVeHm)@L)X-B&gdRPtQ2%@)wwu7WW&FFTR0VDJzC$&rA?r`e3r}=d`AJF?%K|pMY~H%lt0sR;@t^}{uDUF&VD|V5$w?nusNjr z%0hWBtd;T8vH$9dfs8Gp>U?nTP*9f5_wMES#tzG~$8jA9#uVX>X7 z@Rsc^Xp6)&TeD8?)a>84*{RZ};iHxVT$VcIS{#PCYbg##%;g`5rpEP4Y1NIZnQRtI z?>gK(k_|ImGvT**PQtCyJZ8%BHy-Yw{% zCY=f4Xr)mbtkFhfZA-3NkGZoUy45d^0N8Z0zz%@v$a8aaS5MD@Jq9kMH%9$SpxBW? zBzVa*DUM@LM=xwrIlV)7l?hKjc;PagdhyH+#hzr?@Y9!b8a{KNQEauqQRj8A{6N4+ z(QSyKE0szJ8Y}|_Y#vO)U$;sAnOW{CDlH9Dyr@}}tiiY}wQRVQTnR&p8li3&eG=SZ zL(Oq_2XiTjTG7C_IOfgAsX0X?SlgdWeXU}+FgXEzEqfU z=inTTvIyY>eV5Q21AQ`)+AGX1x@cjk&1@lwU6&9#u4 z*HX4!G_|?_>uEqfV4Ue=f?8&&Ra*^hID&j|cfMMoU^#yNCpVR0AF`@bhSyRNRj*Ru zY6Q9nQHb(D_c+sihdb_oSBx=1u)=f1_W}Qkmx{lWFvfoz^ZQtOi`adBTh(0|IR_dM zz%k_&*3FH^P9s0jo}m4uuO^VtZif*>AfHpF4R6YnN`c!5W`VeF^PR}*5s4-ZER_2N zb|Yxl_&XfL?DCuC6PP6~tko0>S1Y(9^Q?j@?V#YPMxqc`VF?{GaT)e5LmpR^ps-=a z8E}*PDVYc#(1xuCwa?n;27J^Jp@QC9I#ulhU*)h7?ulXcLRygsLF}FA zwdOSi2GxW15fDk-?-9@^7%HVB%`eL;)Bt8wb~P2xHYaaG6fpRblVV~NGVvZh{Lz7+ zjDS=Il_^cFP4;Yxnig?B^}$?a|7jBaP_10X$K=R}(s!0`os9YNYz@L^?l(4U&^d3$ zRYMKBMd-&?cO(swN^O9d;vU8zF#N!Cd*RM(zC|((QguYrPRWz|Wj2J4cM*9R=-_f0 z;%&&6kQD~A0%d`QqY=ZoEa(+FuR@SW^1|5-*Mm{N))ZgYbHmlN4{hQcgSf^*Dh37J z3BNr%h}gM2Mihb0n)3^Db2VvuR=h9g;0fdASZ`z~`Fp;)RKI2>)k;p=^npH_zN%=u zQz}gJt;3e5EB?=9DXt&L&7DReD9pdqSM&nyn&;>@aiqT=0t#Yiv{lheIwYsmhiOyDgrp@^BwSiHxMe}IA{ql+rw|aNM=REWN3{*%CF8EfK>x+i z)GZpSlSBwxCfyrEmezeAJqEp4{zDaxqb*9NAp3@abx1HFH;ug36Zhsl_h4apmcKLT z=v(WtUH2q*@MSS?^&+#R48O4JD`IfDtl3C1lp@X7uK zEeh9ELYLBeX%!P)bWX8)5p2yTPw^%XMW#QSkpcP_hkYVZP}Kf}?j+OF|IpN2>=6!=Ui?Zgo14GCEYP4;vNpcke_a88wimIXk!)ql9#uVzuZ_B!XQLZ z`u-Zo-yWg7z%qEyV8>-<8__`ZUCiWmIQ*yphImS-`XYCZ!AVD}wGwXX@p#OmPB#8A z1^MrYe25ebMe8urX0J1_M!G064or4o~x!8TJAGGVUtNO7ypz+U=o^WXL{4eS{VI;B zq=;%76C09_W9CldD}f7x4+h&>75&wwe%zMhquEBcl7vR9)`KhJ;Ja<@&*o(v5#~FTdiQUnj z`Tplt9LA@8XoA^osOJ=Mc^Pw+4jw>|LAWD%B-ubpI$|WKT!R-s)n9F-cch`$bFyV7A%rTxsT_F1+khxJDmK|WkP$L$8v%Pg<4Q_ zLgxLvR;ZW&7(N@OH?jL}dxr(&huA+|c-DuUOO|@kNl^S%$WOU|qpmneFW1l$b~dZ< zkrJ=xdAsWh*Vp%M`O9Kwtpb>9lgoyoB?6v2fL7^VR*k|v9CPu9yP06t}}=OJSJ zh$|B-lv7PhU+d;Ko;b!FkY__SMR;sIYC9|tb@=F4t+!SwsCxl|n-<~bqv5A%Wms{- zIt_NDcsN#q-+Nx@fI&A&l}-~%LHA@4r#z^ZK>gl`H1U%xEzuq0I|L)u1CE(R$E|@= zqyU%Hh7~OOj85oOZK3P*U_Oqt?q-z^?N2e!ipYE{h^jYx;Z-;c-o8t=gI6l5y}hBt2=T4x)>T z-pRvW;>a5jbRufOY<*Nxs1OM)2`Nr7Qa`TGm{)qpCAX60Zy5Y3wp2^bG?pZAFNYp| zcD;n+D zTg>CrUmUXruh#E|B*WBrY+f1@5OYeU4P$P&a+Je-ch-3NiU67i62~}Q6^SHb%SEDW zki0^qp^I<)%5D$&NRir5FXthGuJ7<-Lvisu-Qlwc%*LA}c@bOmrJ3b|&`}&oorc4& z5X?l=VR6z6!aW&e;);T#Oay9$8~17-wn8d_z0vZaQDKtbW!pRVURvX&NFcQ-Ix+ET z?Oj;NOsz%XCMuWcUpENqijQ{Kc0jSE)eNEqSGt&BlC=fihj}{Fsrauyq(4LCNNq${ zi%2zzqIRjv>_)PH0=CFn<`8iu1eu=eU0BbIr+_&3^+QKtM8@IDM0#vG{v-e+`ZLga z8bjfnu+jvX!hTa9l<%0KD1~+}oP$SQ8lot@?KWneBeB@x;nq4FF4n`v@ry>OEaX;5 zjO&lNqUYgx{YcGdW1oN~nFT;#GwJ&w$^@9hRM_i%@`J%5Is`#O;1xBQ08Z>1v;TLf zYd4x$#7aeHWILFou2mV7FZIGf5d_YxDF!xBPArJ1%-I@91m0+y_-M4LvhilP2Ny@E zLg2j%ici8v{UXfPE++FeWt3)1K18v|17sj2U|riYC23)KO1VI5Sn@xdy^(oL7(W`EA8h8J|95kNhl|dGBmKGFtV1^(DXAPSQCn376Ur{8&M3I{~*-o zGL>jn&J3b9-`@W6yE23U=IeISp+!sdU6=OpBV2@`wzg%2H1$$ZZPhA|e%nu-nbq+S zrdF_nIIZPPnzW+p>pZ)ZM@=uK1I9KJD4-bYb z*k9ljKD2GYy{VjUA4_hz2w#Ig z4zfOUjwM5WgM^>npV&(!P$p;cj9b+3M`;opq;G&wER|*XcRoaI z7a6*~!fsDwibH#$$E&|JM9^F*+$3&G*@5%%5TEtzq*NSk7&=#Pkmo1*hWKmf#hfn{ z{!gLazp*~|e(x>Oky_q;7cwcs!1gGn@0!a;7auhnLf&!bM9>OKNjS!fx_igUDuD}| zcWz3tKhA?TT171^jW|-hYdlLGz6SB%O70+_*|bjG_jVM-B_?WplA}H!Wt;%sRa*2c z8B?=D|1{JF?aS4Q%x8uDD%%>DGZdIjf6b~&z$k=TAm9S2B~wDu1sLY59M6XHuP2(h zp@h1xV1heS>}|Z1OYotkp{P@Z(?wg>R~mP1ySe%`DbK4{1+iVZ7OAa($7s`6lcC;D zS@l?X#p?w6Rh5K$IU3l-Iksl&(8gw+mwkZEmrZD|L(%unSk*^sSLxOkYoI73U-Z3u z-51HId!i5i{_An6J3lBB^Pxr`j-aOYN0h}Cm1yfGECu003?%|;VujU~vfTjgBqg$b5YAjo zZTHXH4qu+(e?0mK0| z>+MumvCq~k(ja<8=6!W}pRnaKknMTdMHFdwcDU5}cE0p6cOfQCx(i=;YLK9I?)0E? z_)_l#H4d7zSOQ*>s(=`$*l4JGaz2Y>pVuV@7NFr9(8rM!*X}VId?Zx)UMtkuE2gZV zJZ`SBwmezG^>dR9U2JOVV@Haas(RUZcZ%Hl(|IZIx^Y;RzS2SyFG?FaDk)%)z72Dj zv?hpzL&YOz3otSW!GARQO8clB9Od#rK96MtX)kmp(cAio9-yfUd}uc6SmBE{$B1Cx zRqNf<%`!2FBXldgiC9?Eo85i$2!#u!OZ@j$Gh$OWj>BjsW_NyExHUe`Bl^>k4wnmk zWos_3JU^wa%k|R%Z<`o`q1pZOX;s(FbJyz}+51)OTmPKhz0=%5=GsU&l4jK*Y8D@q zGkNF-(kJs_MxG&SW`a{9>V^``LJaqpk*kFwHDEq*lHn88gi2FMbX+J$p@t#FKOBEe zq$MtlSln6M8g1;U=1>_o`x{4K+HX3rF~0L~Foo4dMNSnC(JTzfQ*jMlN-n(2S>+$Z@A6Bly6gL%lQQw_L(6*DP`L{OW) zuBF1J8B?$745q-TYJ`FDi3rVb3{ssNa0wf2 z%oBLuQFCoEKW9tNQTHV9(|bzRCH`hxelhTHjKaX|qX>i7x6v`Z`c~VjQE?l)w=Tll zS@?$DBqLTjc-3u6mao#4KaQX$%5+}E3nGb9>B3`nE}GWETY>wky* z_Bk)};}`Y9YvltldA8s7UVeyIRO2I7IwHvux`_efIJkGy($5-m@+6MwJ088d+I}2I zoH%Cqb~GrHY@>d9_?*GIMV^h8h-RM;)$P+wRcM#0mFCYu?rr!2-T3 zW!T#uKJ~XnRZk3z3I^PAT!vX~=h~f5PjjXPBuu-^aL+=4t&2u0Vs69I*rkj!@$Kb0 z(o6$_h!VBk|MJw*PMS>gHZgSt*D?mRdujt54(e?)v(gN%H@pA@k=T3I59OT}{Kwz&qb7Bxr5wEsDby=&?wX^{)Tq;lH@o>GV;{R6 z^Sr^>76xgJ<9!RS9x_F(G{^I|vI^{uAca;`n(PNzbS62Z-{CSTdPI;$&Gk+kj_F@fLzM}M8yiP&s! zu7F2)crMMB?&GngGPbL>a%PlqSiZ+FaQjN-)^cVq|33vgqcu0J`MoNZNg`eFk(Cpy zOBz_Lk_W_f-Lg-5hb_S2!3it=QvbCytIXutvWQ<8HPPJfOGMv-d6^)9aMo z#c4)b@;|zEjYJ#CJnFo8mM#D45byDDh9HK+2!W#G{P7~)v%_e{B5bj5`Py2KKj-`> zvdOHkc2ku!!mUr+#a=3>(0cOoSC)?n6e*)UO>R!kHLE9kWowfTj{@~@{f+QpS)tlJ=tg{N0Sq4DK@Q&L( zIwVnmpU)&qTCKJ=ltWE_;lah=HMtz85~=iMdA6_sb|+!ovN3u0VZ@f~v!r=#{jdk4 z1RoxfaS~JYR&-_9axWRA^{BiKKKv+0|K}k8)~i)Q=TQEo7u7w6FSBzLSptRbB`JCM>wag*y-}oCU=KCn%QyM?c=x$na+BO^ zK7E@yp?JTOkA^wI9>LkSmxtfJYuT^8u61vDp`zxvQ%Wt@2xY?eHx2jxBModD+xbU% ztwPus_k%?C>4y)ub&DwltJiT;Q)_820vl~^Q`sJeX^ZfguvW>T@y*1<= zH^Xmmyz92e?!h1+1-+K%LJ zH!S_Y7Q&QKuAwE_mrke6uBnq0Q5JHSt2Kuy%Vb*y=(l87GXz7Lw{0ifVI=j~gJE`W zmarRARdQ+a@sh2!*Tc;NJijMA-{*of*2tKDzRi5HVBdNDC8(n~avKH)eXr=q8q}Ni z#HKJ6ybm>Qc(2p#1p}b9Y?Y0~`4nOnW@*Y6VCzRJ!=-vhn0U*SwelxumHiF$QQt&c zVxG?rb#H(m6!44ItNnY5?RV#Xd6mJf6kLS$7!ZPkd_*Uv>lGqjKf#F}2GqGS$EZP(O>b-(gIVSv(S{~S6o_3w4gdggeJ*k)h&D3vCOI^vo zongyM^{pD|Tc}?wkDX@DAUhf2Wn{ILk4@{Npn}^?LTc zpYUxp;hWEDw)<}Q3Zox3x54VT0<_B}myaleN$b%cl$*P5+22hzTO+WRRqvVSqU~GZ zltVto0beRfHi|+MBqeR`R8{Wfe|tS%>3LmE3f^z`2;Sd&U5dO^ygYQ>g!%gIT6zGQ zsD~g+cKeg-By-$9Bj3tJNMA)#(2SN4GUOs4Uhz%Zm2S>xI3L9bD*c~H(ZMmKgZ6ch zDO1zp1%TQMwc#8g3C8vON+pZxzq4OQ?vfXFoT=mLK?VG|XX&&Q!4b(N&p&G3(y3$3 zwU*}Nk76#Nj$H88H}If9^Ehtig96xj*0kbbT$mj?pwR=aKJaLIvYjG+Rm7*TRZ0#C zU?>0iD9Wj}M8=$HDHrPE#WxSRH127m9Q!mgSO^$eGKn>yMvax|Z`u%8YwM}>;+?HH z*Fdme=$ch+=L_nQd*^w-!_B0T!pkaZAUIx}Z|tm}?%2=($?xr(ji}3V>5E9iqffckTO@M4YVFq0=0iOZ~AZ}I;yxy3*J0Q4Q;A63HstpxlVvQkQt Jm0t`4{ty3!D&GJA literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000..1f6334b --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "collabvm-auth-admin-panel", + "version": "1.0.0", + "description": "Admin panel for the CollabVM Authentication Server", + "type": "module", + "scripts": { + "build": "parcel build --no-source-maps --dist-dir dist --public-url '.' src/html/index.html", + "serve": "parcel src/html/index.html", + "clean": "run-script-os", + "clean:darwin:linux": "rm -rf dist .parcel-cache", + "clean:win32": "rd /s /q dist .parcel-cache" + }, + "repository": { + "type": "git", + "url": "https://git.computernewb.com/collabvm/collabvm-auth-admin-panel" + }, + "author": "Computernewb Development Team", + "license": "GPL-3.0", + "devDependencies": { + "@types/bootstrap": "^5.2.10", + "parcel": "^2.12.0", + "run-script-os": "^1.1.6", + "typescript": "^5.4.4" + }, + "dependencies": { + "@hcaptcha/types": "^1.0.3", + "@popperjs/core": "^2.11.8", + "bootstrap": "^5.3.2" + } +} diff --git a/src/css/style.css b/src/css/style.css new file mode 100644 index 0000000..64cdb30 --- /dev/null +++ b/src/css/style.css @@ -0,0 +1,3 @@ +#adminView, #adminLoginForm, #navbarNav { + display: none; +} \ No newline at end of file diff --git a/src/html/index.html b/src/html/index.html new file mode 100644 index 0000000..a1689cb --- /dev/null +++ b/src/html/index.html @@ -0,0 +1,109 @@ + + + + + CollabVM Authentication Admin Panel + + + + + + + +
+
+
+

Loading...

+
+ + + + +
+ +
+
+
+
+
+
+

Users

+
+
+ Filter Username + + Sort By + +
+   + +
+ +
+ + + + + + + + + + + + + + +
IDUsernameEmailRankBannedDate of BirthCreated AtRegistration IP
+
+
+
+ Page + + of 1 + +
+
+
+
+
+ + Per Page +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/ts/AuthManager.ts b/src/ts/AuthManager.ts new file mode 100644 index 0000000..55dc7a8 --- /dev/null +++ b/src/ts/AuthManager.ts @@ -0,0 +1,189 @@ +export default class AuthManager { + apiEndpoint : string; + info : AuthServerInformation | null; + account : Account | null; + constructor(apiEndpoint : string) { + this.apiEndpoint = apiEndpoint; + this.info = null; + this.account = null; + } + + getAPIInformation() : Promise { + return new Promise(async res => { + var data = await fetch(this.apiEndpoint + "/api/v1/info"); + this.info = await data.json(); + res(this.info!); + }) + } + + login(username : string, password : string, captchaToken : string | undefined) : Promise { + return new Promise(async (res,rej) => { + if (!this.info) throw new Error("Cannot login before fetching API information."); + if (!captchaToken && this.info.hcaptcha.required) throw new Error("This API requires a valid hCaptcha token."); + var data = await fetch(this.apiEndpoint + "/api/v1/login", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + username: username, + password: password, + captchaToken: captchaToken + }) + }); + var json = await data.json() as AccountLoginResult; + if (!json) throw new Error("data.json() gave null or undefined result"); + if (json.success && !json.verificationRequired) { + this.account = { + username: json.username!, + email: json.email!, + sessionToken: json.token! + } + } + res(json); + }) + } + + loadSession(token : string) { + return new Promise(async (res, rej) => { + var data = await fetch(this.apiEndpoint + "/api/v1/session", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + token: token, + }) + }); + var json = await data.json() as SessionResult; + if (json.success) { + this.account = { + sessionToken: token, + username: json.username!, + email: json.email!, + }; + } + res(json); + }) + } + + logout() { + return new Promise(async res => { + if (!this.account) throw new Error("Cannot log out without logging in first"); + var data = await fetch(this.apiEndpoint + "/api/v1/logout", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + token: this.account.sessionToken + }) + }); + var json = await data.json() as LogoutResult; + this.account = null; + res(json); + }) + } + + listUsers(resultsPerPage : number, page : number, filterUsername : string | undefined, orderBy : string | undefined, orderByDescending : boolean) { + return new Promise(async res => { + if (!this.account) throw new Error("Cannot list users without logging in first"); + var data = await fetch(this.apiEndpoint + "/api/v1/admin/users", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + token: this.account.sessionToken, + resultsPerPage: resultsPerPage, + page: page, + filterUsername: filterUsername, + orderBy: orderBy, + orderByDescending: orderByDescending + }) + }); + var json = await data.json() as ListUsersResult; + res(json); + }); + } + + updateRank(username : string, newRank : number) { + return new Promise(async res => { + if (!this.account) throw new Error("Cannot update rank without logging in first"); + var data = await fetch(this.apiEndpoint + "/api/v1/admin/rank", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + token: this.account.sessionToken, + username: username, + rank: newRank + }) + }); + var json = await data.json() as UpdateRankResult; + res(json); + }); + } +} + +export interface AuthServerInformation { + registrationOpen : boolean; + hcaptcha : { + required : boolean; + siteKey : string | undefined; + }; +} + +export interface AccountLoginResult { + success : boolean; + token : string | undefined; + error : string | undefined; + verificationRequired : boolean | undefined; + email : string | undefined; + username : string | undefined; + rank : number | undefined; +} + +export interface SessionResult { + success : boolean; + error : string | undefined; + banned : boolean; + username : string | undefined; + email : string | undefined; + rank : number | undefined; +} + +export interface LogoutResult { + success : boolean; + error : string | undefined; +} + +export interface Account { + username : string; + email : string; + sessionToken : string; +} + +export interface User { + id : number; + username : string; + email : string; + rank : number; + banned : boolean; + dateOfBirth : string; + dateJoined : string; + registrationIp : string; +} + +export interface ListUsersResult { + success : boolean; + error : string | undefined; + totalPageCount : number | undefined; + users : User[] | undefined; +} + +export interface UpdateRankResult { + success : boolean; + error : string | undefined; +} \ No newline at end of file diff --git a/src/ts/main.ts b/src/ts/main.ts new file mode 100644 index 0000000..ab8ae47 --- /dev/null +++ b/src/ts/main.ts @@ -0,0 +1,170 @@ +import * as bootstrap from 'bootstrap'; +// trick parcel into including bootstrap js +bootstrap; +import { Config } from '../../Config.js'; +import AuthManager from './AuthManager.js'; + +const elements = { + accountDropdownUsername: document.getElementById('accountDropdownUsername') as HTMLSpanElement, + navbarNav: document.getElementById('navbarNav') as HTMLDivElement, + loginView: document.getElementById('loginView') as HTMLDivElement, + adminView: document.getElementById('adminView') as HTMLDivElement, + + loadingText: document.getElementById('loadingText') as HTMLParagraphElement, + adminLoginForm: document.getElementById('adminLoginForm') as HTMLFormElement, + loginUsername: document.getElementById('loginUsername') as HTMLInputElement, + loginPassword: document.getElementById('loginPassword') as HTMLInputElement, + loginCaptcha: document.getElementById('loginCaptcha') as HTMLDivElement, + + accountLogoutButton: document.getElementById('accountLogoutButton') as HTMLAnchorElement, + + searchUsersForm: document.getElementById('searchUsersForm') as HTMLFormElement, + usernameFilter: document.getElementById('usernameFilter') as HTMLInputElement, + userSortBy: document.getElementById('userSortBy') as HTMLSelectElement, + userSortDescending: document.getElementById('userSortDescending') as HTMLInputElement, + usersPage: document.getElementById('usersPage') as HTMLInputElement, + usersPerPage: document.getElementById('usersPerPage') as HTMLInputElement, + usersPageCount: document.getElementById('usersPageCount') as HTMLSpanElement, + usersTableBody: document.getElementById('usersTableBody') as HTMLTableSectionElement, +}; + +const RankString = { + 1: "User", + 2: "Administrator", + 3: "Moderator", +}; + +var auth : AuthManager = new AuthManager(Config.APIEndpoint); +var hcaptchaid : string; + +elements.adminLoginForm.addEventListener('submit', async e => { + e.preventDefault(); + if (auth.info!.hcaptcha.required) { + var hcaptchaToken = undefined; + if (auth!.info!.hcaptcha.required) { + var response = hcaptcha.getResponse(hcaptchaid); + if (response === "") { + alert("Missing captcha!"); + return false; + } + hcaptchaToken = response; + } + var result = await auth.login(elements.loginUsername.value, elements.loginPassword.value, hcaptchaToken); + elements.loginUsername.value = ""; + elements.loginPassword.value = ""; + hcaptcha.reset(hcaptchaid); + if (result.success) { + if (result.rank !== 2) { + alert("You are not an administrator!"); + await auth.logout(); + return false; + } + localStorage.setItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`, result.token!); + loadAdminView(); + } else { + alert("Login failed: " + result.error); + } + } + return false; +}); +elements.accountLogoutButton.addEventListener('click', async () => { + await auth.logout(); + localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`); + loadLoginForm(); +}); + +elements.searchUsersForm.addEventListener('submit', async e => { + e.preventDefault(); + var usernameFilter = elements.usernameFilter.value; + var sortBy = elements.userSortBy.value; + var sortDescending = elements.userSortDescending.checked; + var page = parseInt(elements.usersPage.value); + var perPage = parseInt(elements.usersPerPage.value); + var data = await auth.listUsers(perPage, page, usernameFilter, sortBy, sortDescending); + if (!data.success) { + alert("Failed to list users: " + data.error); + return false; + } + elements.usersTableBody.innerHTML = ""; + elements.usersPageCount.innerText = data.totalPageCount!.toString(10); + elements.usersPage.max = data.totalPageCount!.toString(10); + for (var user of data.users!) { + var row = elements.usersTableBody.insertRow(); + var cell = row.insertCell(); + cell.innerText = user.id.toString(10); + cell = row.insertCell(); + cell.innerText = user.username; + cell = row.insertCell(); + cell.innerText = user.email; + cell = row.insertCell(); + // Rank dropdown + (() => { + var _user = user; + var rankSelect = document.createElement('select'); + rankSelect.innerHTML = ``; + rankSelect.value = _user.rank.toString(10); + rankSelect.addEventListener('change', async e => { + var newRank = parseInt(rankSelect.value); + // @ts-ignore + if (!window.confirm(`Are you sure you want to set ${_user.username}'s rank to ${RankString[newRank]}?`)) { + e.preventDefault(); + rankSelect.value = _user.rank.toString(10); + return false; + } + var result = await auth.updateRank(_user.username, newRank); + if (!result.success) { + alert("Failed to set rank: " + result.error); + } + }); + cell.appendChild(rankSelect); + })(); + cell = row.insertCell(); + cell.innerText = user.banned ? "Yes" : "No"; + cell = row.insertCell(); + cell.innerText = user.dateOfBirth; + cell = row.insertCell(); + cell.innerText = user.dateJoined; + cell = row.insertCell(); + cell.innerText = user.registrationIp; + } + return false; +}); + +(async () => { + await auth.getAPIInformation(); + if (auth!.info!.hcaptcha.required) { + hcaptchaid = hcaptcha.render(elements.loginCaptcha, { + sitekey: auth!.info!.hcaptcha.siteKey!, + }); + } + var token = localStorage.getItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`); + if (token) { + var session = await auth.loadSession(token); + if (session.success) { + if (session.rank! !== 2) { + await auth.logout(); + localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`); + loadLoginForm(); + } + loadAdminView(); + } else { + localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`); + loadLoginForm(); + } + } else loadLoginForm(); +})(); + +function loadAdminView() { + elements.loginView.style.display = "none"; + elements.adminView.style.display = "block"; + elements.navbarNav.style.display = ""; + elements.accountDropdownUsername.innerText = auth!.account!.username; +} + +function loadLoginForm() { + elements.loginView.style.display = "block"; + elements.adminView.style.display = "none"; + elements.navbarNav.setAttribute("style", "display:none!important"); + elements.loadingText.style.display = "none"; + elements.adminLoginForm.style.display = "block"; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..db52dae --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,112 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": [ + "node_modules/@hcaptcha", + ], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } + } + \ No newline at end of file