From 2e90f225291e2c1e746879156f4f64beee7ddc5a Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 24 Aug 2021 14:52:24 -0700 Subject: [PATCH] Clean up TypeScript file structure, fix missing VLAN tag visibility logic --- netbox/project-static/dist/config.js | Bin 107946 -> 108078 bytes netbox/project-static/dist/config.js.map | Bin 411549 -> 412865 bytes netbox/project-static/dist/jobs.js | Bin 108384 -> 108384 bytes netbox/project-static/dist/jobs.js.map | Bin 413770 -> 414933 bytes netbox/project-static/dist/lldp.js | Bin 108643 -> 108775 bytes netbox/project-static/dist/lldp.js.map | Bin 414055 -> 415371 bytes netbox/project-static/dist/netbox.js | Bin 316608 -> 317974 bytes netbox/project-static/dist/netbox.js.map | Bin 1137377 -> 1145072 bytes netbox/project-static/dist/status.js | Bin 128057 -> 128189 bytes netbox/project-static/dist/status.js.map | Bin 462384 -> 463700 bytes netbox/project-static/src/buttons.ts | 329 ------------------ .../src/buttons/connectionToggle.ts | 52 +++ .../project-static/src/buttons/depthToggle.ts | 79 +++++ netbox/project-static/src/buttons/index.ts | 21 ++ .../project-static/src/buttons/moveOptions.ts | 61 ++++ .../project-static/src/buttons/pagination.ts | 14 + .../project-static/src/buttons/preferences.ts | 30 ++ netbox/project-static/src/buttons/reslug.ts | 46 +++ .../project-static/src/buttons/selectAll.ts | 106 ++++++ netbox/project-static/src/forms.ts | 303 ---------------- netbox/project-static/src/forms/actions.ts | 28 ++ netbox/project-static/src/forms/elements.ts | 57 +++ netbox/project-static/src/forms/index.ts | 17 + .../project-static/src/forms/scopeSelector.ts | 109 ++++++ .../project-static/src/forms/speedSelector.ts | 24 ++ netbox/project-static/src/forms/vlanTags.ts | 116 ++++++ netbox/project-static/src/util.ts | 87 +++-- 27 files changed, 826 insertions(+), 653 deletions(-) delete mode 100644 netbox/project-static/src/buttons.ts create mode 100644 netbox/project-static/src/buttons/connectionToggle.ts create mode 100644 netbox/project-static/src/buttons/depthToggle.ts create mode 100644 netbox/project-static/src/buttons/index.ts create mode 100644 netbox/project-static/src/buttons/moveOptions.ts create mode 100644 netbox/project-static/src/buttons/pagination.ts create mode 100644 netbox/project-static/src/buttons/preferences.ts create mode 100644 netbox/project-static/src/buttons/reslug.ts create mode 100644 netbox/project-static/src/buttons/selectAll.ts delete mode 100644 netbox/project-static/src/forms.ts create mode 100644 netbox/project-static/src/forms/actions.ts create mode 100644 netbox/project-static/src/forms/elements.ts create mode 100644 netbox/project-static/src/forms/index.ts create mode 100644 netbox/project-static/src/forms/scopeSelector.ts create mode 100644 netbox/project-static/src/forms/speedSelector.ts create mode 100644 netbox/project-static/src/forms/vlanTags.ts diff --git a/netbox/project-static/dist/config.js b/netbox/project-static/dist/config.js index 1763a8943bcb2aaf084154ebbc53098b771ce580..0f5723b02ca556af0bcf43703b53670b3ae3a45d 100644 GIT binary patch delta 4255 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 B$~^!8 delta 4126 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 ANdN!< diff --git a/netbox/project-static/dist/jobs.js b/netbox/project-static/dist/jobs.js index c0c05058df9a43716e1bd15c077f4b532925c86f..649d759ae9a250ffdcf2f551f9261e4ba16cdd18 100644 GIT binary patch delta 871 zcmX|9TSydP6y~hksFj9N)M~fYUz@dYY3(80Jil)22H5pTH29BfS8O&G*M6De9hA`#Bs}?hM?nM&DGDnJb@BwE;3W>c{VfKXM zAl>J6xeHxftwR019Ipx^JF)BZ$T@2cp>cmjfIC6kSmNNysidH#ltz^Q)y~mkoZ56L zTOAx_k-+5W`OSpuK^dOrm?*R-4m)KL+w{uIr6!G$D?`aPoQqR(`f|Lz?rI$W&*N3s z*ghQBJoXwX9J(B!Bt4ZT_1k4*hV(v$}{WJ+p@q_^g@OjdfMVyk*DF$d&bDJ1coPsPzS|qkV!4aL@ z{kt;T)O);>`s<#6Y?|~1^JvA>sZ0)uaI%A|R@jKck?>$|C{z{~C8Z)FE21REP;Tg< zl7@J%o=gJ6E9MTgk_OnVuFR#V`dnrz#$sY;G%o%NPh<7XXCK!i;Q5!Qw(v4->Wp^F zVuvWiMbclFXhLH4g<30;h%aX0E%l3U=kRyX1Mg(hd;Y!*rRiHggw2wZ%ik>r7pfr@(Tyv$&<-hUy4k3@IG1JQ z9fKZ()~+{#0KDf3iB_WB2GrQc1gl)#&3sn}4!QtL)2hPPQ#Yc<%-QGGJ|IZ_u zXJiYGYZ-Zk6n0+e126Fg1R~MOV0}mk%all)#b6{65|Y-tTnu*MJ1Wy!+@il#<-@X| z$SyA$qGermB4EkY-PV5O8&9&(RE(N{0H4MjMa(UWol=PIYiXCls#{c)SgXwUWCUiA zTYpyOxcv_Ar2V`jBAaGo+B#arcrI7WVuIol>Qp}Fa%Ftj8;Mk>Bw4MEDXJt(36vYT zuP#HpmygGQBdWCnz49Pz(B>y|G-I+b7h?&jGoF(Eg~ze_+S3n<5pdwe@fAd&&7JY2 zB6Ub&N+Lb`GtEfMIo)7K5~-OYyrpsO%_RN~M(C|#dC%TeqcmgX`>0iNZ0?(F2~)i) zF1lUyGwtNZjTpZEaWofO*q7O*K=f^P=(01B_UQNafYW?rhdAg5ImpvHeUPo!TCeT+ z(S9uCnJErFS7B2}0C-^fPyjXoGbaLYm$Q+$elr29&9iloi|FQX1005&zqF|7{{IG5^Gdo`Hzs#4V+zBR@O_j>#iL%i%JSj2v>4+-s;dev&?(zx~XU& z6ovGM;sqVDlaxXQ!Pc#7M%_Bup<|aI&mHRhnOW`793C^@_j|wh`+nc|W_!K$>x%3i zu*tr>L;Atmo-B;93+t%mP(JF%Qu$~+!OpLv*5z8PhsVYXz@X^7Wk zOxn12UnZB7B9`PjF)_j3Y@+66F0^+9@I1wiNw^2s9k=Y{VsnOF!XynNW8?8Os4uVx z47pB?dn7kDE-M2M(~{@-uq2+tUPUoLHYXKw-z5ihWV4{xoYrBvvvXfYv?9*S=Nj`) zcUPB`+K&nMYl;Gw3hEV`;1;RH&6LJ?96^E3U?^DFHhn_TYabt2CmoQar}4ZoTeLCz z{tg`rD^~X4HtJX&mWTOn=3@D%N+$gcyv?dVepr>2g?yA^Li16T6n%DWoV4BP*nXhD zmv>g8#k5HA>QDjIc%|&-d(`MlPs#)K_UZaR{p;Dcy?40QN6~i0St~@zNW(Bo-9Rnu z(*}w(s#wzZQjRJ6xq+gmR3xeL-0dtB44Yt49Dl?Jsl_J9O0LY+bHfF zd%cO;Bcm3vCN0~d6_$FAnl8d3437@hvyd*m+tU3{d$J#S>Gk?0K315s!^qO&&}W; wlkm%oXV~r>I(lno3srZv>OViJyTV=$gr9~={nf!U;ged(xm;a27v2v41!RH8g8%>k delta 661 zcmZWmF>4e-6vn;DA=)Qip}a!4yCip;CJ0Fra#leNsEuSUFxh>(H?TXq&g>>0iJZkj z3X4P#SFjN~D-8(S`2zwrg1x0+m4D#O+%6ng3~%PW@B7~O-u(L9y8PIBHN7@!R0F&blL{WP z7d*~m5P5un5|RK+o_QHcWi??-E@75Sq2P(`&I68qC0AU1C$}cX6A#866E~{Ls#Q@N zm29`p!?Csq(&|}<<_XZ3m`2RQj=Q%{X|GS|5v6MAhO5wScfobxF~-RuLM{ZNuMJr9 zmOPnb@9$|^f4Fi|#k+F~PbH5bLgwpbxtU}<(ic#vZma%tVJxWOilz5v4hD%hM5)(p z8G8Rd_~`L?!XgD(0;6#{LQzX)$%Etw<$x*d*a2#~Mnu#Ev ztz(T3xK!C};(G3vR8ZkAn-4Gkz9Dx?a!wYU^7%WGEEQ*8$*1D?7Xrn7r_s96_In}` z*zRr?SFan3#oK3O@p_#s&QHnh)9`|n;l;v__s-gOvA)y%;Dp=d&zurp?F7La-_&f<}w zlQG#}z;Cms(J^_(@MeLa?E*oJjIN9o+l8hxPGPC{OV-HLDbcLXRJ66tE6vGKQ`0D^ zEJ)2yQz)^uRVvL(NlnYlOHEO-FVDX`w%95aXXKZYpi6W5oY{;fY(S@%=wxme Yn8SFGmC@DWg0y08__gxBvhE delta 169 zcmaEUk?rvXwhhZT7=t!1=UBqdsIu8d;2I-~Z-!3Vurp?D7La-_&XS#> zlQG#}z;Cms(J^^Ok7j|O?E*oJjINA++l8hxPGOmzGKW#iA=B2@R;f56zg)?_L@zlf zvAEbLv$#aBC^a{~ELB4(MK>=$FI7p?3RyBSB?T_CeaalhgRG1d+rt(y7BU0r6H6K8 FnE_cJIj{f# diff --git a/netbox/project-static/dist/lldp.js.map b/netbox/project-static/dist/lldp.js.map index beaa000bccc8bd2e97584ad11f6f2a73e1927511..d42cb302505ad87581050f1ae4eaafb0f6971f04 100644 GIT binary patch delta 1531 zcmaJ>O>7%g5LVW&ZVTa0N|Xj7F>$M|9jzQCfMfM^qu?DZy${vX-A0%; zCCm2B8$E}iaI)jdtSW&{Dr7;}NlB+r&U0AS@pjUufhrcYiqJy;D8FV@gn}wi#GyZy zMA!c%Px};Cz6oqUWUduNddmxx>dWe)95jV~o^45`x;WNpIdrmvVgXe8{P%I~d9Hm) zCay?dRywfT1?ZYiz(nQvh>4Mhii|s?>%-?hBzLFI<4R$r_egU5njP5J zZO0A=`3mnnAd@Qx(7aY1B`io~P?&=X97Jnq2&=g*w7!}HJ=pd3bad&vw#9UGj*8>A zJ|naIodfMje(pDIoZr4kCR->%!&Bj6dV6LNFw20zgFOaF*2ifHy`3G0MdQQv?I5(> z9SA}*WMVy{NA_#G@B8@6;6InOi!Ws@JLoy)Ky?J79ajh_h#_}9m+5lRB@6IBA10;} zpzD0*UGf6|;nLW7{@26A)8`a;i9az$UgV7v0vh};`m93p-go2zU%f+~;`|;Nreqoa zG&%m;7^?ov694Ag#Ki4KCuI2OOH>?nqeM#$ zgHl>EYLspmby~8}hM#pxSB330V}n+EMTw!bykV>gk6onHE*e!zo5ngVykW!;L!U@1PZ(Z?Kn6KI3eE@jxA24w)dhq-`< z_!ad+4_N562?xIeaURD+lsI~`NQH$ zy0_ALJagh$;X9OzU@~zw_dAPO%v_2;Jo+)2_Z;}q+2++w-JCAOvSH*-+9lJh;!{qsK0 zF3+4fQ{Q>#?KAkqyN*8k!J{p*Y*B8kmV&vKzLMe%Evn&yHJnp}1=Y?o2On-ZDUFsM zZf)MKsd+l|aJf~YiHFMXcG~32$;@*nxv?gZ@@&GhS^ z+;?2}?YJV36?ps31P#>Np9m;1RkqjzjmBFi^n3ST(Z9T-bLVm%75qj0DOFcobgqE1x^69Ge1)8rj@iAK$vHqQ zwdvS>3F@eqUNYuF%>iX7En8@BKrxxD`D{`v*d2}eE$N@$HG0?_z=|r0$!OZt z)2m6>~+?r9XI9a+s3mZ#}&&1J+0;TuiCy0v!n`o}wJcV{?mVm7??~RsPvxAL9VJZzRwY$lv}6 zK((!|*E%kXj(ZbyqTcafKp88bbcc;X8j{DBdj?0>5bsY<>p$@=nsg_ZFa_gL)JaFdh zv{<&41YN4nH{&xfWeE^FzJp^)#;1S$fU|Rg@AHA$obo;cEWzqn%|&IJk3CtPORwI2 z@MPZrA9~)`(_6GPb}uL|#)X#67%OJ4Bx_SahGwC`+a}bjzxLpR*EK<8rV@0eKG1M= z1Vx`R!51iEK-Ip|SeUS{M_)R0VsFuBjS9XH@#)&3PIE;NirEC6s<$>>H1I3xQ#7<@ zpQ>(@UCmLSJYA&VDwZlx)0Wj~{Rf9mY_Kxq0skY*J@W}D0kWU^gf_@p4 z+94n}lc3|Gi}6MSt=-ikZc$`BLFekDSoSFnKJnqA0DxVd6N~h|(|dOh7v~Frr97JhO+1yd zZz~xO2nL{fs3zLfB6VgAnq+{gC}?rPLxc5h{eS~sg-Uf*yvCNL4g+_oJ8#etCr1=F z3c7ttA0MC7rvx~`$wd^CA$hd~lMuZk%aE0{f)-3_D{90<`+&Xm<)x!J>eJ{@j*j}c z*l=%iQ3j4`YqeB?yiZy%BaNI}C8NVks0sFk+#6nNX5NmWO) zQTtNG5X?6d0hBN^G-3~KK_Y+(OGE)h8zK{ zf|9n4>7i40lGGnR<=b$z`zjjYLXzg8k@_^v#x(7b1_aGDI-C%@ zKj)#@dhyY`5{+Q8LwSSd$0K!|)W5rXFT}A6QkGA(S^vh)UHV^~K9-6{n65UG($e;r(ZnatY}+>NlUMZ5ooTrlfeB@l`30I6%3q!LQ9`tI`=kK#Q| z^NKpJwYM)ISAJkv?>T?*kVl|m(L>8(Xk|Tj{(;^KhnGK6l{nS1;^7>kfF~#X>9SHN zTf><sC*K*vN)*<7ZO;Vc{6%CG;!Hqi%fYh2%aUcq&07wa6+dseSQb*B-Dx1#>st05PV&~!SP@MFzTvX|r!6Ka zSk}MXa_;JcSkIJ)j@Fxwl!cZ`qh+P@;@T~19U8Z+oz$=C|4f?)v*r1C8EdvsVDQ|Jr7Q&Gn1>G4 z=eJm;OC_`bkaYkmp|X%}>2g_#@GaDj_2TOmC@Xy&x;{V^MeReiExSiAnXG3P^R`S+ zP+82wV6wK>Uj|-+=i7pcyr_TNq>z&S8`F8%3C>>hl7ODPc=pavS&mQ>C@ITj zIY`4aLOBtt4%sD^q~xIqG2{fNHqH8t|2&}o^TpwFOX6$DLp}8gmCK5YkIutKcVdTN z`sJr1=F{Ztgut9-4;`zQ_Lqgi4%P3EE>XqL7X(Y>$mN2|ruq;>wSEs>tXHcTsBB+s z0J)+!wO!hn5Zf-`p#$}@-m*dY&3VPe!M7Aqyk(9@VKtwp=7_QsX=_`GSW9Xy55q;C z_C^#lvdqZ3hvo`&ENAbH^vJn_(i<^s3Nl9#6At+X?q%cQoH&lOO@=nu_X zEcSuM#K&@NZS7+@v0iPtmR_+WJ`bI)LtrJMOmGBp7<Wa?_aF;!Fs@{ORtn~6Vooe*z*7wQxY*d75>i5vWdRb=`+vg3zU5RXlcWjYM!CnU*}Pg?n^&`zwL&~o zEck>tw6-={wo8K?9nWmYt&`F}$l6*>!P3CTflzs2MKW19phqxc6!-Gt zJrz;F%m3PpbOM7h24FR{!tQ|7|GoR5I_AA%2CqQ4( z7}wokBn9;zuWi_!yK9tcMt{1(b6*bTg=C)OMmQ&c5SIF z7cDfaxXNbx6IRb@G|KLPu^)Hhd96H z?|0Rzdy57Bes`Va4RB1~Av-*@5Y-=iRrBtFDt;GqhwV6L<@$fp!(>3;*>UgQ6|pVc z9-0i$G6nb;xwOtPF4%PJtO>(W=>8L2S%#N`I`QMOrC8>S;R3ES{zdho(iV zeIA+-i;;PnSa(RLf>AED4tZ$4zC`0?rL|RNhp#oA)@@x^$McO=$II1?7Z%kL9-6FI z8?JJ{xhhJjeq$#W{L0*IsFlQ&kLjK>yZ5O68_J#C*ayS+iso|1093D zP;Wc$H^h%1o`8hCsF%C1jOG21D}w$ykfUNy6Oh2`6_b9$suadxxJ{?%H>^q#zcR_G zM5Hmdq+kD0_vIr+G}^GCYf8kgU%7f(3j6het7bVXx>)qkc)g3LUx+I9c%!MPUw^~Z zOB+i9{ap|_>xJXh{-U0C%|NZFS-N4qqsvjQf_9gp?SuLD zShT&+j-;v6WW3@###Sp|u&frW<(yVfJWpL)^lY>ZJL9wih}&?3Rd{R10d0D>pxqUzkrtLGtR-lndCgZcq8=Ls70h+xzw2J8x{v zCdN^;st=khQ+L277_QTtqeei$PZUYEAEvLCYS zT%Hc+_203b+~@Ple2|>!1^C_xTUL$ooME(f)#!afC}C!Z@nT%%20qqpKNT_bUJv!x zhwk?qt`&cyc&pzpvmf;|oz%a7*GxAYU2;y3uD*KZJSM-@xm00YNfZ&Yzq zk+6z`PJhiaR~L%yjz*7ee-HP+>7Tg!_z|z4t27n-MLXPB;nn~B?&N8w-^PJZn`};u zY6arAppnCU#x$rqs;EH>VbpI3rbE@CCaNps@f#*T2*(MIwxIRFLMDe*aYmKaZaU`I z9XC#H(0H1rgH#LJ!G*Z_4SY=ta+btj(7q|HRthHQSz541PGc}p8UM5(m~cASs=Q>j zs2>Mo#CIF-sLQY!^hFiqidA1*>+{RCeWypRA03FN=L^9F!?skgpv?jhpf75)#Lc^M ztH+OOQ83-sAwSxug&@+S)gs54rFo>@?)X-LZTHJva}7Bc-} z6=bd|$cCbd3!tj`xBS5vSWN#>@WNX^yl((5<;7XyC9%(u@9NGjJRrukx=g4@wyJI!8yob1rA+gm5h zetF4HSNQ_idi`=Y-6Vobf!Aj-l&17<9Y;A2bkkAZoy3B`TmugF$5_ z`deVxo8$StlY@h^0iR<)|Fv@9F1Mx(-;U(dU?9SpOb2p`sT52WL6PVba^cm=anX;? z>NjtkCX;~4$-Mw*hURI|vO1_gaN`crt3Q3?Qqy$QrgsKU>1#cEbZgIEDc5*?{>GlB zsV$%H>+zY~8g#tmyy@o6`ffu;2G+Ux%mT$`=+(d4bLY{eD1fDcFkWCCPh% zNIIauaP#EUK+ZxrfNlAns4Xl{<3km)c$+}av+fL2*#B)QZ- z?nj$(x;!6WfGZ)HS6fE(l;i9rQ55XCZtq&I;dW8r-XJ7>fqStzzu^ZA2NfU3y^$&o z4Mq)Xs#;N!0td0~f)lmW_NIvzD#~Eg%1z>Fk*ZsC<(Vkdf(Ff2EBck*bEgUwVHl;W zT#d2KL_vdc+&ny|-`^`s>5BfU-p{&6qE<&PCOoToMZAFh*~7uQYlA1N*k}>?HFT_L zdWAUWLNib$^oOU8j|@i*xlXGXCYMN1$a6GTG3-Q{is2c}W2T&v1o6(Tk)CvoG6(Z{ zEtaNp(T7`)U$d+6x@OSoNw2MGl{zh!g;`)2i|t9vt07zWI$D9JXt3`x)QU{sJC5=E zb!ccXX0$4IV?BeiN59hFDdj8reE+$DTm@7$tYq-8XKv)}_1KW260FDomjRghJU7eH zh!X5U3q?!KyvEZOTSWg@e;XOrpY6ZA;Yow~^Wc&dLC1$ZG#9TaSmBBxiWDnKxbh;Z zO|>{oLUt`aU#u)BrWKPt*O<2vVjyd130mlUMg-EdtSp$SeMJS5%|U;#)jNw+lod{} zr*lRuL$um?yrM*3(s{O`OzWQ>=#)|w{bvL12Q!VnBbAC~nU;I*C>Ec9+FmG7#3% zZo<^6bBl|)rN?MeOf9KbT(1cgMa$Y44cF%zsE8m6>Z>SmzJaDdMCVQIz6x)QmMhAH z8tboQWz&78_Ng5IXSxE>!e9ST%QPc58{`hIjauwU75)K007jEqKu}vZ=nw=4YfjB3 zgJD%JSEvcb0a|Fg`l2uX>X-ETx;@;O_9}czxq>Yl*|54_Gn|UZvFMc$HU%=T!qFKP@pEH@EbR7 zbir-os8|bXz5wrjPxoE6?k+fjZL@}5Dc6z`tC=68vHCpvDuy>=w7P&Py)}4VzdUke z&!~VmSU{id9Qlkp))>obI1^DF1zRdEw*quw_}Z&)U3HMfL|4f%x?JySq9Pc%eL2pJ zih9z0m`vyoxX)Vx0=(fd+FvibXpk>^f1}sd#fq-%Kdpb$eQkD9Z13V2_0?;*Du$xC zR51)WC5QvufMW)VLEHeL>8%*Xs{xe6lF@8h>BU}5sf8erZl%J_BYI+#HZKQn)AP*O z7c{HCXY{mXr9#Va#rFjZK_ixBolavv)s;fHRd~2X>yrM%(Y}pRkXJ-Bh{y}!tri$! z8ZZgJty&EfG4-45%l!L#4ZY{H=>@AnYKHpO>C|uQ<-czq3Z52)fwILUgUCwl*%#3LNn=soT{#{fbc^odW7JXa zF72(laP+6fY*N~*e{bwmU%%K#*)iHzFQ*yhXwIwnIUZ-chJ`TiH7tlhuj1wTPIMuj zx@+7j^%=XaWqOy+#>b?*SO3oV)xFL*Hy(1i(y6yiv`U&+$I44Vum1XpfulLE90B%n zgF_O#Bc)_eYohCz6yMQilfXrkLUNu2sCeAc_R6sh5~D57zaQR6NFF}c(j9Tk%s zHHIkp1ImN|Pi%~i)u)}TLLXR?ybby^5r=|m=Oz;mw{iOP$NjC)jXvQ&d4PX{9f10R z^F#3kuEFZh`}e_tvUl>*E>9f!_A%aTvpzbxha~iwNgnvQHaUA~NsOR0MqTw0guMo7 zS&R!!#qO$sbmv~0sR3e9zc|$?g}u6e%5-Ki4hjHGsIF`ch*m>mw3jpGP|<7XLJ@D( zy%85)AsY1>I#1lIOmZs9(WTTOYD0DqQ)#z_1@L&}SJ0Wsv4yoR0_0xIC$-dh{t zkQDLiiRrtgs8|0V(?L?!cg)|)#!6g?@p+Eq#Qb@DLye5kU;wjmL$>#MmD#wE-^(IZm*1%$Eyk_fc1theNunJt&7B~f9Td7`1?JPKDaP| z0I{isX)>a}ccJ%8zgG@Je}v7=$5V!epdvKM=GFJ!cMhIC%YA07i|0P`*^!*Q?B$!3 zn?|bKAqC3S+J%7Y*WG6#EBgEIyF;l2<#3I=U^DP_h$yHVu2+gwE9KCb)i4#}s|}8n zDcuxMFu?l*$F8ADW66^BDiI403&JN`66NF2!y;BhQeh+075yE7t0wdhVSJVMo8y@7(KvOA6jv=BUU>{IUt@V1k({tLWB<+Ya4>M=Vg58=tt#Vsf9Ue| zJ`J8heJOMW9>F(Hv#$yONr4=`F&fO) zl(t+}A5WgxmB}hyon6$UXOqVck9i?Z-^#bI;lH^vh64#dik#?L=6Z`md6$Fx{L>*>(+pa(cOIERZ^E$cNwv?T%EoNq;#*f<$=>W^hxQ1`>xhd_beraAZVF;$7loc>d7 z07JDec0+AXE%xiLTbevI?XB5Y6~*Iiw4B#l5|@w7faKMvO-m-rW!X7>K5-olJd(J% zG3Mo}m>@!FWf4&08p)P$q_s6yJ+o&CZCUe44JsV8uXN8<+dUTBMB{rVYilp82eWlM z#E7@1cnf?fkI_gSU&g(PsbBXekL?N@C^O7aW@he1D5E;KM%K~3&{Y9~Tn~0c4IC-( z{4Ge77Vle-NOLrhDCejnk(6=h=Tp~r=N(GU0TZ4>zdvfCDmwJ{#m@c~ zM4AwYH1Xdd692Fjk)*|-o?h&eq7MD_OYKtJq5sR`g_fv8kf@}ifjz@pu&2ner#a3G z)g;KUz#cSqD{&9Z=1aMo8*^LHrvPZZ1bx!=%pMJmy##&K#`my5pGX6JB91Eh1O)o{ zp?22MC+n!9&pq)h4<}D%BEft-0BRi%6yeN2Y} zcTbhFihm0_E(MiTmNTa8+*NR)<3=J4C2W zSfy?Vg;_b9r6$g2VF}ihkp8C9SrXIttp=#m0fNQDNUebYkIxDOl!Vrk& zj9wXiQ63&C=Or{{6lZ_BMtZ|p_ce0mcv%Hz{p zd{G{so@>kON;p(klVv*eldC?mq$gL;sxU$G*c0dxvKu>NB8{LE5dg%qT4J!})ltH{ zbyfwOIOL*Ap)TZZUE$u_i|RjEJ#~HH_Ply^j2=U4bhCnT+`v;a(PZ?VwG$hqmqKjG zVZSZ63NiDGvN^2nXbrL050vGL9}bI|HUU=k#s5|AOiUVB$c>BeRm(f9pMdykl;kbqQ00Hdc; z=SVgatoz2PQta>)a0`!^qcMQxEQV_z>Tj?Nb4^0+6)arK$$1`sAMDa^JoxnubEDJMq#7;Q zma@Xen$u!&K=vDTCMsvw7Iowr?nB=~LYVc&ui!sSt z+aWJa(Ag{x#JbeqWtgL88*S8fTvfU(DE<0Zf0vz@;Z=u&)rjbktYMQF)F1pkbK7vx zI_dFEj*WRI{QZ4_S!E=<)q%wEsGtn$pZ~qPFQI7fgm*4I0q)^T)nKXT6V5c)`zlZ^t*mPy0@6&bG zST#(YU5Hd}e)%1{=7LHXhJYk^^rb`LOjT;4CT@`q>o0%#)ywIWQb^qnL)aHnw}FZl zlKMAae(s(=^qa(2{7)m!&jE*kyUX6!1x*$m)mIO+!`Q!qP-==-zvC4Si0-9V+WUVmTh-6Zr`5NKladRoTS;c2ZiB*%PXyU{gtmC zA$k34ubyo3baCtAQT^s?-m+&1a4$i4(31LhU*jj>6R$mU9OvI~awJ2GJhpfp;zgnh z8G81$$M^1Qk2^8E{Iw<4?E^y<2$GiHyX=NnKcfAi4E{VQEO z5TvNg!b}1))9e3rHQX`}wRJ#;iO)?_FgI=>{i=yY+zHA{E{GgZd~*>0j2}_(zemIYl&EJ6pk z`8J3CrYF0ezWdF8w;hVup|>tV2wQ#YW&HhLuw%YqF6D{BJD>j z$MgsOq)-3jXWO3s_doHFJudNWY>@`=O}7LPykRmtNQn9fWNJIFr;$`t@bAT^fBm+$ zoqLu^Hjn^jDsI*$zx*>VpH#E=7p)*-RcM z9W!a`a{KbUQ-A*lj}zC^pZlQW&>pksYjKg5tFy4_sVAxa+E0()Q@*Xwa-+{iQjFT@ zm2Ybor?g8?AODQ);5Mga)%mplYhO6Hr&o00T7-(kr;~d2D)ryiw7=1GBZ;QXyt(D4 zKmY89_Q)x*q63R`TI`SM2K92f8zP6({q(z@J4kj;iY}-0{XcKfzxkus-eK`RyGX~y zw)Tyr6w{@7*cxDQSXjNfZlZ%J5pr4ejgO?LDW-w&&)k-6l1=R+sdf|m^2YZ@&n5lC zKYrqLDanuS;jgyU1mtnUD*dq^x9X4l*veTI;J{o}u;`1}1|+nf8-GWVVE5L;vXh^+0`@BNJ? z#b@Gd>yB2BpU!RIfbV(R%5Cl)#AZH2~yWn#!!WjEj6bXxzj=TA$S znEsXLhoyW>KlehPG~m|5FAVMpib4eixgUJtrj(57KYQVuB>c$N$?CRTOjh-0etlvW z0*N8t_qthukVhpg#`Z}hz8516Lf)roxu0!FqlD6EScTxE8ECh z$ucW!CwJ@zEP3DUCdJgg%>HaUxk5(SKW`^?KDu)|$i3UcF*z7x_aS)@1JzJ?9hL*Q zWJcM$c8~#pf&FL)IosSA4=hK;T#|>mjWW|tauqF3?j&u16kFd(D7t?4PSSzgzuQT) zQ?-##=5U(~Vk>J~W|YnCB4;I+n^ks^7L@+OUBuVIOGAi4No`Ar^oqd#UX2FWzTM=E z)T^;iUL{8l1mJ3^5$m1+8{SP^2YP`I^}Js8mffU{^lpB3H<>(4oa}j-m>N3CpqLHt}db@6zqs1_?4-x2F)M3nI$T^A%e7mHhndBcf<2+*Uh1}m&l z0@+FXsHd*^A~3)&I~y`B?L+*S$U`COTwjL(R;$_)mi-iDnX9F;AF3XLZqi|XZ-Uce zDd2=uxrOWV{$>6ABfHtLE5yvE&l1=tS>Y^z>D+weEP0}dxY*q-WQ@2r|Dc5wcm4lp z`tkpd)9=RI2Cfh_?8M4c4?sB^cmUzvxOIZ()nIJ0^f+z)1{v_x`>Yji-BK75IqYCO zWL!V|$Z=-IAu{kyoJu|LtyA03tCPLFop>;eueFnIztdv#!zd^GAh60gE$dv^bSSmH zWxr+B2mC-pbuFvKDGO|Y@0%N9&^E8)QbNYN>|}G4m|R%@szi!XjleD!#LRpqSyH`O zEBL_FHcVM3YVeYq*JMnnb zcznT1UWT{vPSSkT1#OPUNDC>2r?3TM@rBpP&(~X#z%b?}m%fE^-0* z=k6kQ-ufSOlRiAY*i8nGJJ;ouHMj);UcLa#a+S26c5-W~qX69`RK!`4zM4Iz#mOR9 z$z6MA1wrs7X&I+~-gcV!*e9-%V@JwKELq*+Z0ac|*}ML($;Mi)kygo<)Sr6n)aI>g zB!7V9*fS1td`C{DmHFJ}_Z{S$d-mZ)ttxq5_P1_w$Nk9hA~-Oo*3LfSbS%tk4OM(m zrNeZnVcqi0FRBYT8L!R{cXV^Zsp-}&Q;*1%2hbMHS!|0u9$4frvcxD!?iBq}FZ-ua za&q4y;uRrg&+vAcp*$4JY0G(-{FYYU|3 z+uOr9o@iMYOw^13CE3p&A0ub(@m6i4GXQ468cow;fhSO{#(jpB8C4!eK3(K+8b&L_ zN|OC(jHJlWW?-BgAfTe@39^GU0|i#AT25fzi>n+|b>7Qf=_97SIaL{$pINZa$~pES zANgA{#9rqo$FI+(X^tzR>6kj2Nh8>}FH_H$<<{?MjlT;+c%&qfnY^k*OY9qd0&hIe zf*U@~SrZP87Vx@QOlAEUgoLSpINotlRa%Mhg1E9LhhylYNu;^eGsH8I5M`J7OCb}B zfyb2@0*#*SER`Vo^>O_pz*GJF&n*7)E=MyVEeUK3@F-_LKX8DPd(-4NTc0A`H-`e`Qv_ID zD_JjC+-waJjgTaJb%-pI>CK;o$TqSqf|AVfQ4IEQ1Oo`Ot_V3p{LCF8e|_ACIag6Z zQ0ABtlv5?vu99;oIi!+TVkN(-l1?dGV%wsm4Eba&N?LiVqwI+&8XRSxh>`_N?0k&e z{zQfCy*_-3!tEiOK$g!hgz;~VN(Dk__ zHkn5Lh>&S^H9-(v#9o~s?WDps6U2tcj}v5sc-W03xrYQ;IZ2+9k|idm$fQ&(v5gdY z7a(j&llz)hG@0#BlQZmXX|k~`jOnt2pChL>9U1bLrla110Yy4w?B@xkck`z?^hCxs zTMFo7zrQ5+^GJxX44!OXk(_03ERruBz^;i|R%19j8ZPE#HdZ8O4h?0H*O)_*y{SZg zLweaCmLZA^Z2m==Bne(GFOzG0*Nq@tbL-xaClG{Dkc$PLfStAu3}gZ;j1iWVDa`20 z%fQ4Zm&p{FV%t`T6A#x4I8c!#S4ht(G(Lp$;hH>zYK?aCrOi+A!hp@LHPVBJYmHny z5>SfJ#TegE^y)vVUA7trwyNYcPwQ8F9un+!iy26TDkAjTLrgo%~YDm)P+K$fF!^ zTNi(N^P>-t4@gq5w3&U7tN;r(zxTUjNW#M3`TOLLk`oyF$8E0Bj$3ob^mL^i-@MM91C(WDfj}iqc9@7}HNT7soWaQ7J zcxiLwG4he^Sm~XQW0#MzrN>D(-u~oq@`3YHX*mY+m&Ce6{NkJl?z9*J+F~_Zr#AC{ zOipkpzWOcXpUGh{uq}J7cXREnWM|WUAQn)%O5j=dJIP7*&bN_KzVmk9`pW$6Yi-`T>wOKWQ@M)JlA=i!4|E%*TS!!Ty&-4HLW z!AL|lrP+TxMI75laP?fAJ^c8#qo)753Hy=c_=bY~hBognhe$nlK(?%*qOgmS4^mXc;5` z)!7s8C+F-@F40V;w}7H%&xsc9orE8(0Ldnx%FzN!ZQYXK3BPyCJP(iOHQ%3 z{5jdf{^KLylup+6QS=dD{*Qti2R2{#QSyZ*(ziMP39>G2!$e|CnIWz0H~%+TKjJP? zXQ__-?ozvR^EH13+(PwVe+r^{lsP_4Zf*zY0xEmz)8yiII+0PL?7g2R!(@{E^3z0V z0>oHwhMeBdJ#Q%2Zpv0nRAjN0&yeF}f<62h;yMB#^Dn~WA*9F=_Px)Lb0d&lV61Db z^C(E=sv!#f_2W#9iYF|p}qNY`OQC~QC~vH93DWLg5sKllHFcPz8uSIL7r64(&5 z%wCuxSJ;uiA)Y-6zL&5cunIr=5;@fc5>%Csg(DZO_Cr!_YZEM`#!VW%cs8H@8?v8s zk57J$e2*kHpZq#`q3MX&wh--Wl(8kS`LTZ>r<;gx^Y6bwq-~_k{^!4tpI~o2^RMLM zDNhE+6VlmY0SBVCQUMR!_iupt7+d%^vbYVRQH*{3+Yruv_-|xxJ71~>Dje9%e2e@9 ztIfjSCcgk3zxD6n6JxC9JLCo)f$u;MuCO-#`|)?k*C7M{^>@j$1i4RrADgPe z{_FeX&!8i}?+4_@{#=7KAyVo&^p z9NQ?Tsc~8NSO#)fPSgnIZp-V%#a1G}5WX$nU7J}N7eV1lh;YG+OIqN{Gv-ru@V zICREw6I=;Ba|WnFL^4L;Pt#}zMkb1AIU3g@MnAb66;TPiYF@>zs{(n21%FDK=X*1_ zLnR7@O;pU}s5qI&?*%s6olDbfV=@Cc93|ccdC&Z^0a0zRmhBZy88sd3D?cU2o%tMg ze{~(roDxJ;zzsJn@&TElnTjw8{*qAUbML#AAMc2gPVWKl>#l&$0_stfR`Lv1} zD2M_JbR31mP)u|jr2u&obuMKT(~H&~(dYPF8+5tLhh^u|{7we!EPf9iuyuZh&HRj9-S{mCk>rHe8?;Hh8bh=j1_%Ky3C{Q0q=aC`ci#u8-Yh|n7dT~J?whERL zI}^KTC5h%JcFJrLWfvO6Jj(ii4pN}82Y*gpeI&*siUs#o6%Bq&i1X63vzgW ziD%S4viL7Rj)vLOzkuGZvG4wZxX2Q__Diz18|CsanzTmQpZ}6P%07KXI=&hCFY*_J zf5{kMF8>;Y#>ZauYjSn(Qi-b3DE5=h%s%^Ta+$>04}VQAfebbOhWz3Fl`Ug(v5)+Q zyiZa!mUHV_P5RBp620;feyK57;4K@iv@Q_`KJ_mS5~ z%Lhc30W(eUv7v9!!BgT^7I1J%{7Bm)YlA0}30GRltf|_cygb|~p zaZ`5#LTUELP0}?<)7btK(%B_OCy6{PV!D7MI5^lo4`P~O~ZDs+ol@0X&v%ktU}y#a^>RYQN~Dsx=Ji ztL6+^syI9R+%49a^E7+zvUGwy-zx24zuF-!b9^oBlyDM&y>qA3zIUaL@YC#{c1nX# z6x()5J?JTlM`r;*zY_OU^9T>V|VS526m-Y;EfMf&X?_xOuO>@Eu>`F zd-ve?9GvW#J<|$kMEWGA?G({+qO^oJ#621?~~p`2HDvIl9fGW zl@2g{zw`%2#up$|edmB=yE0j#k?M(GA<(OkHnRMhgpz8*>B0tu8Zp|B9F)dEXg+jM zdQ4JtZ0wLk`SXE8l7~OPbV%ylqvo&-+}z@4I}b}kheux2#~=$HmK;(g%?^fv=znoo zYTrLt;&;><9+(;S0oS&(FuM3H@Tj-I-l1C)($mCY);ZkKmB`i=c zpwx)sR$Q2sNhaOMXrRdoLQd=Sl<#-!?-1K@RQhVWXb|fj-m=isjpDqZsGwm@w;cm! zEwQ_fVJ()}+A(SRoKd}~bZFI%{WwliC2l_U+%YWW5G8rF>HA_(`onv2a zmaI_jo^O`UUlb&)txXi{0Ug9OS-j2`soBYTj!SL4UhKHUnZe`7B@6WS&mEUKo1Fz- z9v2a9so-V%PDr8s_(Ua08t6&kfQQwl*=l9zktwg&K%r>^mo=*27?i85jvv z4N(mm5>a;MlynxG;GR>`CGej6PDvxj#fTf{^1?8tXO$)P@l(3Syr%#j8S96l`_BQvb+v~(Z30zW*(9zQMZBN_Imr=>d&f$C`%!IXIT zRnIVc?liVXn)RNM%rJGw_#?w!b4I#I^6Y(QKw+oZ56?(_WSre`7F&3n-8w7Xkjg6i z$_1&LeetYx4Jbj*Nhb7WK8M-^Z0H;?N@EY5lTMrf0WJvwOmR>ct1?jNF=uQ8>@Uwr zH@8i}!pq2c=@bdEGv}qVyZIJ?e*o0u=6T5u(&=RnoR=955>`HgG|*wnD;7stdJiiRA!aELKv8DWRdd7qfdh~9~T4z#8brq^g4A=L+nSFA@G3spCxGg z%th(+F4T-8re%&P7p1nl`c?SmYEnZB9B5U65{st52eG6c#mtA*UA0d-xyMO1a}O&2MUTGvr8HhuDfL{lb;Hr7)PTsrAm~Dp zJ%uJW;C6I!TZlX^JeZN%1q>|_cwWUBw=ND*tGG0?;e8F)MYEPLE;EDrfu7-isS-ku zlElv&3G=CGoesmVY9GGQWgqV7FghPn6&S*YE%t4wHpE8Sq=2O5*n8R}i-c8v&sFIx z`$?O027Bq?W$8^PhPTLqTv-u#O!MrMmnExI5NHtraHnL+d{3E2O|1r#h29MO(Lr4G z$uDex>@uE2Kn^fD!c+V>GlUdicM?Z`f!)LG1G4lzK>Bfu1OgcH80UW) zUuEDYv;as1X?Eg@w6;x%L+lDhqZvrbC6q(#2UkEO=GftO>7tYlvAf!(KL_dfVY@VV zP}|x=PEK zzXcR8Fq+GALVnA1O2Z?-P@h2a9_M^z!PbXAK*L&I2Y*7WI~PHC23+{;TDR1@0sp*z-n-DAEOht_9pM7^*^7*Eyz?ECH?{-3 z_n2U|2YhQ5caB+XvlOO?9*F%jsA93rAle1(D3tnnv{fY)-1i{VVQ95giGj;Nwh>;aLHlRe=rzf77$OP6`7(-2Y{9%zV!v}ua#E;Z zD!-NpHV>#^a3k{jgdkSPK?R-RKshy}8yp63u^(Q8P&dh1?~p=LKD7D!cYraU9&bp1 zN*?>Y#*A8e*(dLm?%y}u_-^M{=COghq###1?vj3l$NB4!dEa-p)Gn!E_KmxxcR(Wl z{d=SsKKLRL1vW|wv&yn=o6 zUI{{h;un|X*=tc(0AP*8K##>0Do&Z-IR&dD4gwg_O*Y(RjC=KLE_PFq&T&aikz(j< z^RtQse|I6Q406vIA7wD4%)>C}fTb!;eL%Rh_=vj#qQPRaFj_+zTQF2LUnOs^L1B=C zF%LF07&LRa1z0$F>o08}S!+NFhc-Xd1I3aj&N!q4WB@rmwIy zFT4d=RM!2|rjvWIeZ&o?G#+B%KIuChkr1q0nXsq^51^LM^84BQxgk?rJYu9&9@S4r z*>b-W+v!g8(xdF#{n7wrKkI;WR@v^O>?BF06hm(~C z!DsTU(RnFq(Rm_Bn5V*^E@;u z8)A%=b+M4 z{rcIPy;1_M!Chn0cS)B0WK4Q1NwG)AC3>K=wb2NBW*qQ~u^*314vhJZ32F0k3cwJ* ztOg-S3a!?u~l&zm- zebQ=k4(Cm|U_>>3T97YLUdcY{mu6vpobyW)*hEkIrF&b&5Nl)#o2TA-OtcOV@0pa& zoR489)#mt}OSpUsRNlxy;0tq3O0MR{TyO*t6d&@w5@&Cjgrz6P{(Vxqj;>8p(ikbR z{1jMDj=gD0%A5>_VH?XR7zo%0emDg+GdRo`*)&Z{-zIHCg}S^yUQkxKJYALehQ!ZH zVF~fuJFIh7I)UhqXdj!OeDEcQfe(@}Q+ze)Sp zho6-W9Jj1hqZ=-B4=b1F1sFs54iOW`b_S*W zmzNrFnh33%R~LCZdF{6Y!o#M6lE29nvWVG8d5vYyG@W6)L(=8FOCfYZIj<_P2jACp zoQ;R1qdP*lPRS_N%r-*Ofy0jD8YI5yJ3)zJo{8w z`XZ>v8zR!xrsc5AS|8okyaO{ZujbjmMx>8!^oQXV3g$96&&Vme0h>m~m@1%S=CcZ* z=oaYoqAV`B%apm)3|CFwumv?bKY*v2 z#uZsu2`Ib~p!ArE;g-Y3d-Zh$pV2E1EUe}XA}7TsF6`Cj)eTW?rk0BU(^9Riaby-U z#c*7>)vEP}Yngne?Ob$0;m)K2BYK9m!rGZQ=vkgr;-{uEGid6yqLlU8b zz#R1@Td!wVlZid6N&mAcwoKVrOH(uZ`$g#^;4|Z!picel{RuQZ%s!g{y9u)&CWJGh zH7Ru*Pc4f(IIJ}?&NY%un0e90=5kVcHIFvio|aw<+V&v$q=9l*xYQDc%4ne6 z8ZHfm=@(%J8P7x6k0lBPxS=L zZ644&`4kEo_!Le8R(c{WlMrR+^XlT*MnCL(FfN9A1X-_G4%%U z#UYx!0qYr}2X0_nSjb6d-($J-j~m!R77Rq@e}P?-z%7AEdSxlknAc>T)D{Fz?BD+q z%b?~oK8JhYc6fUPi1hf4teSZ|^lvx9C}pPQH-V&C>DW!|N!Avot=T-2{9k0Hb9LTz z0PN%GFX9oaQ<4Za=%)wY0j1gSCDxYc@Iy5#5HO35eTi)sHw7I@wBk6TM{Bq4X7Kl; zWV@N26R$0~g)L|8`ipXMc@nvAVY>jWjXAtXe?F5wdV$T-JITIP-rjdB%Sy*2gnsA} z40O<`TiH#~cZ-4UeU)V@W8uO{y8af>k~_Z4LJ1a9ZezKEGB*jz+(*T?G2?7AR6AxX z{Eq{-F?F%z1)wyA)ew!{$pYZgP`xZk*ffIyn)?;@))e5sN{4?JR2&0HrY^VzIPL%_ z0RORy;h=>7Dgks>G>wCQA7ITCuv(DxQLO@8wfTUtNcoG)c1yoO4WYSY**f51Ut`ZFxBnMR&)>hs9%Wr&Qa@t3yd|vBgvGAFG)B|Lo}On z56pUI%D;zwC*9%)qoJ`4VjYF42C?Aea|NW{%gT987{1S+u=RBKUY3>^@?-O%k6(t} z=$D7sYWnU7CDHrXzpFIl3W6lxQ zuwd`>&YQC%OvWQDV}4$&s+Mj(!u-i~VZ^d3??f2$aH*%fBV5`|^B;iixiYft0k)HE z7>{zJVnq>OUZ%$@u=)$ja-F(K6G29-A~ru(0FkEX&2PZC-A>>SGAZ{V_Q1S$+cYJa z<`LE?)!}{n=glMyI`Px@@d4WUFg|@dIUZ*1SfrO8W`6@YeC{Z`U8j0hO$Jm;y2asz(z*cD`j)dD_oEE2kQ3(^TcyRh5KcGVd;+F@mU>&0mkFnew zZ2%x6Vu{d1hbUN7))#2^NI7AGEz)r|rXJJqLmoky0BU9T3l&mIj# z#8QGxN!C$T)SyD!9A(jh=R^^kqb!g3je8`tI-@n?cmT)Ko#Ws}WfKQ;{Kh-rA~ zn1&Z0c1;bguyrAf7xq-Kj#||K!YO&d_729Xq=`>;n5GqKRPYi{=Bm#`%TBYE$&JCw zi}0SeSPtECnl0f2CS8ty`ZSwjz@49F@s&7KAm#Zwa!1&Hxaj;Y!tR47%&pI{?nFmG zbpPw;;JedF3!Vqt=`zvU=h^4XYZ82J4w+s+S0j>T)mToY10p=_1$-ws4xV9Yyc2Ns zl`jLXpqA`OY!yaH+V)*`H8j<4oPn%4NQ+)%uEe})$$u_3g*pMJfST{T2xfiI6k#th zTY}EvYHzskvT$8_88$`>Ex*9lQroxKhLjO7(^B*v0PzC74?+b(-(n9X?L>!Sck=;p z4xMGQ;hAJP%c}5m^sLk_e0-LzPZ*~quYg#ON6KDdTpXz?f0dc(qq9t*J70w&*%3MY zD$7YwLOAS6C(mNn{qt*V1q;Z<$fV-e;Wg=_)33AD+!xTMy;O>JBKwWhP@`G}C8w`` z8OXZg+wARyf*k@W#X&WwzsP$9Vifk6AX8k5FJy9J=vqH!GR+kp(4*3Be%(po|PM$kc&9CQhCuzbGe zxrhuTlQ4nsbF6Ht1^sAbbi3$r`fbHpWmrde2#tbyHt9uRyDayitVy1-*b_%SWJP~G zeULS6ctXcol=HS|LmiFQ#_(OXZb$tL8X@nD(Vx#js%)Z+w^(knf2Jm13ZOv2o7oPl zhHqU0(JHCyJX_1cCi;DZFoE|b%~=Ubqoie^7!hccGD$@ zH9~iOpEWSQhjQ}yW?K3Kwm+lIq&8h1N}u=vtIDl{7o3bOqVvuIgW=hr_QZp2Iu-tq zEh*TJl>vw25jHTK*z#PY7eTZNjlD&L09ZWIgzjI1aJuz}Y<`yAB)2@BHA7d^23;5s z625Ly2| zOHN?6P^9WdY+nL;JNp5vE)9l+vexSgI>CwzYq&UFM&t!ygc%l?=s`niW6|5<%>IWg zeUT?LxocP3x;=<5DtaW?D$XN z!WAc5Eug>rl>Lp@nKS{81w*vxBWSMo(z=g;v#pWFk632{_Mf94W8-s@{Xf{oNY~GB z3l^Y>{|Q$DCtdk-Y=&X_-p?71C(;AIU_ZxpbJH)`HFF1HO%fP7V5DvLfR_L3FWJ8) zgm6j^o~Ulx{A*}z!nF6-jIe3U`3+b*Ht3{=U$Gwg$|o#c5rP|i{0TJZaJ2aZGLfDB zMf~{aFP}(NJswRLNIWaD^|w${Vw-;8Q&y;i{puiH_>`@k3#SNZ9w(48ml8f>_X)_k z|1*Y24lT6h_jsZua^LS+H;3N!5rwa&%zwwbhN$G<*=tK`Oq$gi?#e<)aF*jB8{W43 zPppJTOA*_jSVDrfEX@gBk>~z{C9(PTK&cOYP9m9xhxUKW@(QpHh!RuWCqG@AaGF7v z4x9zQxXwXoe}zouz_A~=YCzreS9arUy*Cei|68C~t)H_F<_^$~zk(uO_#85!n-=|z z9m1MG?_;2M{swlrnf~}U;F_7n|IS{U4LTMKfsWDd{(*gc_dj3+#WGC(15VA@CEba< zkv{le>?a99v18t7hV+a7LTMV*H@k7b6#R@du-l0Owz}!EM?Zu>1h(-FpRhO_4`4mD{d=NZ5@TsxRIL=S!nO!^<356=nUvjWDz zE9QQ}Qj}T~q#v8Yx52Ri;QR*IQGDH7naMA9nJ+ZOzN`syyBj5|;B%bzd z<7)cA6o7cPh_AaHcjMR?3!P8m`49*Hn8deE+4_uoq*V^E4Wl8nd1??L6XOwHH; z`Y|a{U*vLnh7GD$Z>INd6<^6U-CVB?h(^(Iz?2zZ)J*cwb-GZiIYL2LK{wG;4dDNl z%(TjbsW}M_f~9oP+Bw|Cf_-$)9Nxk$A&u~Y(NCJos}xsXX^0x; zawEv(m*?^?L+_45KYSPPJ2a0UgO|_B`Fwq%1#ZbcYMIY_u?L=<&+`?h6-O?9G@q{( zn&%NDw^4f{a~E*0GUW;tmKSKdc@sDWOP`99__~`!pijgtyGX_C73Qv)bs`t%%vu#% z678@rG(-Nr7^@&mf|>?pA(VAc_Vx!R)f{MGDJhxf?7&M3#j`@gf@8LE4+i9e(a;zL z0esF_uZYThsZ!ocdz1MZX6vJ`C-XL-%n$1|nH>>qQVQS+=9BGu;4jNlzn=+t$`Bj5z4&ENn=jYJLN zN+VJIa#+)<{&3%_7`9A9KTJjf9isjCXzo&;39I$$rMw{>0rFxvF^vRmbjwnnmmHYM z#S4ENX6>VYS;{9{IE)*V#&1mCg;! z*C)9Bd~&DrUhatA`gJC*Iy z3|_GmLy=5KK(`tSBxoal28h=P-J8KX@$-iaUXxTUJ*=qW2wR#q5Q2(MxD&<4QyC$0C}ttd58vZ4VM8mP-+ zy#xiyvEN8+Ps>l<9LXyGHX>wR%0dF1b265A>gPDY~4jM<^`GS9@WFuhkj|oKYNt?AjS9L>|nkiQ3VE z3a1KbM-Bmr>;s6gBAld!Dkt^?+G{~q0_aK-??JDz0y`d(qhDsqo_1}@&UTq%QMY-h z{&u5PWK;LH8~yDChC$~X7yJ{D0L;@)^RXt(+fF3~*wmfv2OIe^Ds1H0hTxERQ3!GF zwZ(BE5iuN{RgVMF!?B#MF?zIsZ`kN@U@5hni( zG_R0ntu~FR6CqE)GAPgJYL8Xv@9-f{vuIvLjc8i!6R*IgEVDJ2qI8+5*CXF^EGEA? zq9>Ic))U6}(^G{!)!aB{9MN)2Hp?qGS70S-5Y?pLQCKz{&EBjUv~ZVD#7?IU#yPZ$ z62|w_;ZV_^Py~SAX6u~7!w!kzr4qIe`79XH-s-V$g9B%3z zM#yJx(AWqYVxuJv9K8T8z^pge+h7rab?Pkw8YikPs8Sm&=syk$wN_{&j^3c)^udE# zYwGl=!*E3p`ox>GL^o~^MlqFB?-WRZ)^3qf9-|c(aW8RuRJ|?+?pkZTEn*)DqC5Sg__V6WQgx+1 zL9XIp42#!Di#G7vp_4wmfxiqIxN9SS8Z5@5EBLxAoL=d0py$I>D}2WFBC5$gO(N31M2WxYnyJRbx;oM>=itf z4|$9S_AXwA<3re_DrxPNVD`;EV+VOR^OZY>MAXiHuODJ!rFX0i2A5Jo0?~AT-U#@XOL-F*kZVh! z!sw)lQtn#ZX~9W&%^oh!v3lt*rRda8xn=yl#ocjPbU^DmDd`#@kC&dmhSzsj`(mi3 zog_rCK1oKqEds{cKuF4vbD;{qs#c}XY7{m3`D3C!xD^2<1dewgJ*rO}7usGSNez7M zjl4LuKJFzNwax9IQ$}uEy1fF&gbI-}9{7ec3(_;NgVt^3>sB}TqKApp4r~wF3x+T5 zsh%db@}h!(7x4yq;^KQp&%@>B2E3E1t;D!-Q;8ZZq3>-4-Zs*_a(;|8`y^h!SXn4lCPevK3K_b!(eW&gyAtvk5@ty zq?H&NoVo`g=U%Sl$uwTYmuz|)*0rrdFQFwpAyUrL?NlioTc;mXONwi++SjB zu|PW1_%k(3`x|+tp%ckK#W77onH?|5E$Eav>sl=M>?0)wEfzSFzu3r&mi9+;EQwtk z@zn*t9j1Ra@|?y2$+?N#RF`2c>q?BsDS<2|TFNyw57QGHU?nnBiQk$MqgUJ?a2o^o zEvq9}6XX}q*bjSs@vw1A!=MNjyN#1zx15qj?>CGYgohx&vEA6%BUqZ2Ww&gRfqIR0LY+^=f0#OwV=jhGdr+2dFLh z<3ny7L604g^iE!vfYsi!lMgYUmn=I0*Pr0ek~&0wM?MABj>J9qEgPhaEKKFsW znJF}J6kE^ANBH5XR3I@dS3y4UhLJtWF3k!~+bVikIDUY9M+Ux)RL$NupZIW}2ePVk4+n^?Qp5TjM zjy`gN+Y+iHi6{9|Va;Ct1pgMqnIAvFU9(2y+Uf@*7o1dqacRr~|_z`EdczUTs8YpXY~J*i3yda5r?D z7hV9f@1VcD0B*%WS!ej#B@ON~`cYA8{dK1Ga6wHspz`k5!9SeVT!=P&)ETk|Ghe z7x=l`7%T&c@Dxs|_yIp3{nSoLDGCAbm^^k3O-&vD-TMBYj%*6s=fy));YuI}2mUoS z3~A(vR={GX;Z3vxO=k|Y?Y}e7DY3HucWbB(R2!lgz(qq8OLtL4Y|L>uFlwICi0S{Q z&-{O0%PGLXB_qA;Vh&4U6Bu<$EwrX(MC@mX^RUWUZGnKHQ~-t+g-eS0up$b0HI$0) zFtff0BBP;nbo(VY4W$C|^6EtjqM-gO)vKU3iIsY}v3OsDwDhG>5 zPGZb))T;SXr0+Gm)hf4I?N;mEYNH#fNQj35-D~8YT}gjB!&kR82Fr9Z-!AMi%V*NeO^Wo%|s__p#@41OY?zQ}V@nl95I z`z4;cxKR+M*y`j(nqT67QY>v!* zjVC8mjL%q}F|*oaR@=>Lg;_P5W6NWgYF4-FB9^AwOuJu)O|qJ9cpcn-H64AO@5Xk* zzs>ijG+bsT2fxjOsnuo(vT*;6ji=s>!>v3G$$2)t!LJkky{FGXKVYYdHz5KJNAJ?; zn;cQgsqq}QU)-jqbH+p(%<8aNt&f{Rznnq{*(V`K+J&b0lIaZ4BX2=KuB7+g;yc;6 zS>$reL$U2VZ)UY-I(nX8$#$CQ$LFCQsHD2LVL|Ieo`tu0+vYkaLaHG*z=o^Pd(<>h zfLcrM6;P8K1`Yb%^&2;B+N}q3={D29zs)CEhnf5r_$b?9rr%!RHBAF{C`32l_`ek! ziqRUEcCS*#2?PP1ZW0nrLNl6ZTaQR<*=45v?;t3Td{lz^AU*UBUyYeW-g<}cOk6gI zB$CB}P=5eP&ybw6M7;uYa3a^jmUsCef}lO{F5j5iiNu{k3t_XHp!eEy?W(+qI{M|i ze06H~wW(BWez0viJ@4FWMD9FoMAc-mgkE<;@65V^vfUdO=b|37-J_}ZY|!hc}onw z_QvzpvURy(RY^&Sq13QyaB$EdS7ppALed}Da59aJAv`aVJknhs@M{;M9zHSsLtvf_ zP6~gUU_92{Fl`7@;Yv zb|~=Mi9G1QGYK`3zc>|SAhAUD3@alE3HHd3T*~hgz=VFsqxh5aoGQT4xf?nv7*m|a zUcAJs>`2IqkXQK$OBjiS{K|C^4x zpI*LE*@Ei7-lz;ELL|1{syr(wTE=b41#soRx=q=NpZwdE`?s1vie~N`9$OF!J`rxF zNR!g^9V9H$z{X&@PkgmhO1ncTfCN>3hq4w6GjxZtViuqss>UF}f}Tw$?oe*0&;v>` zwgmH?N@-S~9e1N{pL>AC9&KlblY|%Qf`{+K=xgYwcPhux?ag0Nx>vV>JV3mQ+dtZ8 z4xEy{svOWB+(Ezis?xW1?9xWPCM4dltL=7eSDBV5Y-Bv7?3ry5G3@g5?R4~zvSl_t z9)g!o~hDzl(~gf|@mHQmMrMFt!K!@IIwfF^Q}6L&|Enmp*<(xrM5a%k{hY0c8~p zJ)q1>slLo`aCajdOwT-^)U6TsORMllHQONt+2X!igLqV9Yr?elL1ks;P=Ov?2Wm`_ zEU#$5=EHgR!a_4y9#ry`U=?(dUw%-@Oqbmv#G>dq1_>UT^-aExjy!{1(J8JYDfQwd zQT|>R<-Y$--a2QfKzwrBeu6{#|yjke`cLDWW^q+Srxr@gUM?P+GkbLieQkFLE0er)x z6P=}$d4}lv1ImWtKvk&-!e~IO)L{|!Po(OBCweng_wmtqIkM7STz$Y?;z#s9 z1g1Za&ONGhY=PK-pvIb?n52F?B||)paRE?u|AU;)_+jc~Wan{ZZOKl1RK+4-QV?IW zbVKUC`1ua;Jorg7ojR_FC}kfVSFXiR`D2QQzVK~GAVYNaF=b0i|78FJH2akTkKg&Y(wSkpOoIZ$D(3)Q`#ARSAzFVz*|V@uwwkFh#K(Ep`gQtZ0~T`#fAhY<2?G!No9A+j>{}e_Y=y7bN+n`YMcJ*ix|uq_^BYXu9ija7N+{kOP*8fWtbl>C&E z&Dvq(!xl)`0_R8Co>Ke_sPV?rN+_W=a?LYJCxiI~j9fN7{j4G+sm#-0BIM5s>N~A; zCR81afM0y~w1P9^xI6=8--{8nX{TRAltQqai=I>RxY{yv8AKjtWRBH&mM+P&2>948 zURX@GpHbQpnj#;cQQpF_qsWPul)Z`g&3X;%4r;cH3;{qvl1fO%%rjgj%ZvW$&Z(a!Tq?HUhq zOO^)l2N{;6>M}-OoFeU{hI%;nicNdPj7{6=w%Bb@(iLJ1|2(g(UOS4Nve+8(3-w^T z%n+%XpsrVi#yy_AgOh1^TiLWudp$yF;$897#V_6+eL#n)lW!|m&eCZzz49#2rs3Ow zE!h{8MP~1%?R&@FYO7tXv8&B)wbQMR+0_oaT4`51?dlFYKBC%VSDi@KJ8}tCZAZL- g3yM=|2Qbo#2YI$qj?NF9eQZ7Rjzr4dQEp25-)ePtApigX delta 49513 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 I#B~$?FWt+hc>n+a diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 0314df851cc7d75a5f7b48be309b07c4ee921efd..acb54704c78a97a0ed331c4e159d3ac3ff206b2e 100644 GIT binary patch delta 7305 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 Zo>H|b&Z148^=!P!gTYEaX!6wF@Gq-EZI}Q6 delta 1490 zcmZWnZ%k8H6i;6Zw70kAy*WkDZ7=?bfK8a7R6!QsD+L6RO2LAV;iDAVj{f1jR|F-Cq(Mxb;KWYNU07g4211jH1Z}A-}s-bp=M&V8g$SdI+>vwnqlI#z9 zBw30m{M#@!idR`)bmW!7qz`Q~Yx*=M_{oQsukGa}g;!KL6z$3@r7HMAEu@6#3TSc= z3`x{Z!b$YH5wa~dE7>uKdeT??Z-+0W&?S;zM$w!B7Ut=jTo^<7aP$_ok}V;8(*z3< zoI_rX;`bRXi%I=KRFw|i(};tiE|f{G9z~y~!TytIW$Gy&MR6wla1!l-vfF4GES*7{ zEQ^yIuyqFUs5aIc5EH5zi$d27Du%n$ICuQs3?lbttd~B3$Nlt!kY#)!lYF!j-{4@r z4X49_J5*fcEM_5h1TTZabXyikdvOlD@fHii80ff*R>8Hi*b4ic*bWDt<8|=sUR*@}tibovVTHwJgNA83@M$f+q<>f> zN5Tiw$ZRL}F>vNP+WFTwZiAcO8!RL+fhUd7auFjkG=h5>xPA^$6VdxpBH53LAx;+?~#J{G8p` zD>|A5AtXB7g5cLeLUgzUR}nn)arI!>&z>@RdquKwkbP`27P>0PZzJq-BNJ=_Cdpp5 zH8~_fDAZd9M3_&q`K+Lbs!K~pTpCjto=vc~j6zKxd6Z=P47S}`ZKK|@K?G%jZC+X_ z2wn#bDH(N+8lkz!O;*=&(*}E3=UNcBVTf)5N-L2{Ga%s{7TIko9 zouoKrO@t2*v-u`rd(Z_BkFlGXA}?7z#nyhzR96#UJ$KP$ zEU2s|3sqc!$rusaL9XDY%uX@rcGL=+iM5iOHQ1b@W<${}#^F{KS8Ng*o#mvUmh;eg zdNrh13q&})lbgyE>Z+6xC>^R zxn^T?p9rNb+?0KTet*60$tl8*E!?}*(|`-U-NjYggL?HJ$FJ<;*1$pwm%(fgg3`)4 Q;ZiF-gc}KI<+iN+8@_1$SO5S3 diff --git a/netbox/project-static/dist/status.js b/netbox/project-static/dist/status.js index e9f67d1911c284c45b5b2c3acbca787330bd0bb6..a2d02a7995289acc67ebd34f2137d717f2d5945c 100644 GIT binary patch delta 2980 zcmb7GdvFuS8K1QcHYOPG6JrdL5cZvQI&4D>Bjr#PY#zlAz>9gP3ci(2(oy*&bax0N zn?hPA9RdM*$y^#3(w0EmNoi9qK}{HvG%wOl!ITWlkW3s((uu(km`OWr+L@-`NwQ0u zzxvO2yWjJ--(&Y)87(|_v+%`zg@|ZR6s`bzxp1MKa2>o7VR<4`-87TT?n2R6EJ2Zl zEsZqO#Sv{m(egGzr^FD|%?5czpy-E4Zb2smMNLRfFGZMc+P#BMSdv^*a|1=#et}O= zw6i<6pdA#^P8B&I@U@~wM2zAo41&#);=U?USC_1-qas`qFboV>VILV}uy(eX0gcgO zi!~dM^D>0x6JePHqV@@rA~TYZw6ub2dMbmk)-!dT1E4AqQD*J!To`afV*x%YFp?UL zGFCWIMi%E2vH(_>!HO7PO?@0aKyo7cPB`)prZtoiF6$=f&VKuWKRm5UBy3nvI%1L% zp~g*GxeT*4lbmb>`^Irz|AgAAENfLvO~@0>zAUptlvG9d4_k(4T_sg;{&30A48kPo zl$qTw4pW47Ykw>)*Jey-re_4%B3MA8t(jiG@qS(gx*!F+LR6A|$g~|~x>~{w3UV$92$LRPH$(g`U#qjFu_1GJ^of}s z%4%mh{7xvt7*k)3In~$XXWBTK3e&8r1iC^YvzxHtaDG)V6VoY`wFiAs3e$ehMC5MX zln+UI>cO7?UD9F$x}{|;!o~|Nk-}Qc#dE2mCQv0Pl}Xv7E^IU|tDaI2k28JR@PZm6 zvhvg*bg+)xfJM+J69Cp7$+$d z)o0x897a<~b{CE_J51MFWM!RJylxku8d`UHGC*9qX(`a{o4f^E<4oIx&h0p(%{%;n zv2gQ&0+65cHv_%rcY`7GNVC}zTeofj`s&ur5VySdVd#~Oy}vaD)x-;0KH@1sJB}ZT z!_*i@w?A)6yz;zBmA6ZD&Dki?^vE6>=sSDbftK#AheNpbCQO*8hUNhMeTIHE zWQFyV$Lc`b_*g+f2{b~O1^3}FOfc3S-goeSSjNZuHY2Ei=01WuV>RG13ySON)a==@ zv0?ugX78*%v;^qhLrn#3ap?TafTPJL9p0B73mWcKBlP zJS=g`QZ`ib9LebpNAFAf0EJ~Pc8s{&&e}pji>!=7rf8L_kZb5j9OSI1TiE`JNTr zTzYSy05%;L%*=1Q;I_dHzzR_z^Kklt9^Mr&XLE%}5g19@gJB-dUw9Tw$i-g+Eg$Z# zBEELNgX{8j_#5nl6YWK`-NPdg&VOl-d7P2UP-t!P`YdFwC0aMvm1jWL*^0kuRC1WFatio=@n#x)MQ$yJ^)GI1sDxXGjmP3~Az|KWR;vQH4|GD+*j!L+ z8EXLG{8+V#GIbk%gp40Sdv3L?FSiU?5bmYrXV4eTRzS#)t z>no58k@SHIbQ@J-d9arW@?38qDh9r7Psz%lJ%#myRp@O!JPVcT>t>O?#Z`jaV04IIYj^6kN#0v|k7T7}q z8U`Qf4H5Ku35Y9_=yZE24sg8@F&Gpidupu!SV;QXTj)xj-gF+7=~5U~=vUuGK5*0b zF0!Q8pGU(bd5W5ze--@@g6Ze3p=aRJ^8xCmzZsxJMuGo4E616COYlhKt_}spMwNm> zNR))2&7(1Ynf)C$D9Z6D-w%(1O^Qi^%_G7D_eX^Zfz9Un8Y`pe0)KE9MPkYS!-yUn z|6{RuMM~6P{Qym`B@$%|iaXc|?O80yQCD$+B zgym01Q9aUM9)sndZ=y>`-+c#`e;7lj5en#sM^R<^={v|kNYz*0MN6w=Dy7g&LZt+n i;Z;hanW#$1G!xLDy^BVXtbg|&+J)5gzwRM-{=Wf~qy^pp delta 2846 zcmZWrd2kcg8J}kx3?>+SV~jx-!rrx3>jPq_l!vNdb0j{%Ud*8?c%QVE)|FR6yNfWg z8OYR0hd_XCXEqHCY05F}q;69-MNJteX&@o(3^8#A&rl`~L)zhTNHUpDoA!_NTS<0l z`_HHEedq6e$6Jlw%YE~1?(2tg5vRS7yB_FxZk>(8ZM+gS?9}bJ~hI>smNEA@)*TY=Bn;ihhF2Fmy~%)R^S(P#n`myY}!gOB^>e)>0fhAn-AY zc64PJ+D>uW$Q(N)zA>jBaYo);2Ek!|r>Tj|F86MC}(OMP?)+ZfS=r7a>U zA!^#C8K;EJsp6zF*tSi}24>7QrbQbgYD}IH_NRqyqNFOqf5b9m>&P#I`v>!f7jjIT zPMFp0z=Yyxm-bO%v9@plvmm8NH^BlL?db(I+a8srpbJuJTqI+2Od!m427Hr>2&pEKny3kuc2aM~Xi><2g-7cPgAg%^@6IE)pR(i~IO zkd6w5{}nd_nQ6r>Y?R`R{Gw;gmLD&!+-YJokw7UQahZXU%AazgeN@Vj4+I`DoGAyy zHf>!TZddz>0As7oDY3yMI7(PZ^}Ui>pwE}A06J2#8|bo9d&B<~2!d0NhT3URy^&Zy z|0pgkQR;$ACre}56`6XO&1hw8shh#rwwZk~nitohyi{~pmMTsgs;B|CvlSZH#zVUR@;Z!R>|SJ+csR#8x)w5qnT1sGpJ&e$|P)ICo$^Rme0)zMVWqWY(<3; z+AuP!3LMNuUmuR}3LtPrp#WHv+xd3L=&pAXt*Pw{48_g1!$3prYk|Dvi3l}_kUqh$SgWgv$O?O-k(eU8iP3WW!@Olg z!fO*&RTeB}p}@#RBsMic_-Vh3XpJW~LL(9<@57RdEWFkVCDOX*MO3N{pK{E|A{p8E zsrR6-FTD~1C-l=!0B3NgH=DIOdzQ~4ism~t!L(&@`uQK2G{Ocy(%h%h17EQoc z-*8WSmJ3$PCt=u)p~+$M3fZ^pPU7}>ZN9ojN1sDc9NiAi8B;PG6Bl7F9cjJ2&#ox; z?RL(&JHr^ETN~y={MYX#Ay(-AI^chKe`_hcI&3r&jS4aIO{=O>;Prt?D0{FJ%$gq5 z0&xC8xrs6{1%HH$cc&gV!TjU%9?Kx%dtC+h`ms7C+vA}Y{t@hCU>a4CZM<&w9p=n}w5p0lBMz6<$+0;&Y@uz}f@Di<761#Eym1L#&(a&Npdwugq7r@LedL8O{qG}7 zvhxZW%g=hspIknHeget#(HrO``04&L>d}>(sL0AmlrLSF%dMiUu;zDkT9Z6;6OGJ9ir#q}H6uaSZzB&9 z^&7X*-{3U%8G5T&Qel4*@U^AFf?^3<;FVJJSO0?cBQa_Dt64KSdHD|7jPy%)P+8J` z57i*O_#Uw0eRK`!o%ey=JcZ69{X0{@mOn%WLVo?~7idkntWpZiL{v(knV3pRG{dWu aOfzA9>6hp(lJwlK&_3i(KKm7NW&ax!71SXB diff --git a/netbox/project-static/dist/status.js.map b/netbox/project-static/dist/status.js.map index 1d0eeebd6fe50306bfe40cce7a439a210517124d..cf69c347cb8ec8dc531101caf0585e955e0d3827 100644 GIT binary patch delta 1512 zcmaJ>&u<$=6xP~aQ(S(uP~28Z!|MiR<7nl`feUV&YV25{C{D2K6h(2QY_j8I>3Y`k zt`nP5rH4oekPs4rIfMgTdW8d$53M+JK>|cC{103!)C!3=v$H0uD)unieeav^z4v`H z`@=UAf83k6d#oKNj+4i!A9Xj)Pcr)S=)oENiB|ADpdRf|76#zA!De9hdiD@k3d|3I z^%3FspwArIb{Tc}+MI5_Fy2#wP5v#_F1^$;RIJc4au)xNX_t@2v3ZPbU28Fz1-A_| zw&%^n47nWKmI;7ep9LXo#ynQs+58ri;`bn80hpVMLj~;Gi9hC5=Z82=7Q$hd!aL2{ zN;R6%5;O7h9WDEEUKYJ0*X`}?ub1Z!!OnEzTU+UY`aMoT%@=PN1d0TWrQ>cTSk-=&iNkAd;Z?cX`tGR zUP-j@U-s9KLnx>MNe=UWNlf#!UGt}?78+yQgxwLpGJR3iDMEvH4E+*cnbb3{sy3B$ z@(^-1sT*%ck$jX#aZAv)y?}~A@ev0mS`K%jhQ;GkAq?I&q_T!O$l&H{HP9 zaXmL2@-MP_wgw+TnftCoO-zbo=(`Tw z=07p*D*yAZ#Mv_u97??Ycj9srC13Rwg*dBSI|!&_LEyt71tjd_*$Dl;J&(%6LI-XT zx@-@E&TR(mj7(%@NDQ;zoAKMQ~HbiyrGYbsLTIkjDH=L8ZUQ^*FQ;J z__#Htk6Kgdhres<{LM${lSF@M^y{PaLh`7xVpU0nh^1y#NM+R`gxs)h5VB#_NX5Yz ze%1(C6S4QL4YJ%VM;JnuHmo&~am$3bWowy`hP6&AA5<`mTdRcB#nzftCuAMd)j|2G z;B`bU7Zjl^fID}d0zGplayC9J^XDI@XU}0_ERW?H2z_hmV07zo`ihnqY>YmAlD_xK Ezx255Pyhe` delta 727 zcmZWmzi-n(6jqbibYK7^Xpvg<285H=i7O$Ys%7XUbch6s_)$QLQ|S_)os;adb!WE? zKNN-_Mg|5CFfnvQ44`bl!j2I951knSQO*|!MPfMJz4yNFd*6Ha?QrSstEDHSxlv(M z9F^W_R3A1t!7%|0!cRPKc^Cj@1E4HQVh}N>%&XZ9R=U#ID{@_dEAQ$7fKVh_yUO&&IH_+-Kk57lgp(aYue&4^F-Z2IlDLV zim6n@MlM^K=Ha2V2wdqYhgk?v<0SHNkCd(Uu5DLqw!LrLqGNd`7)Avw3vQ7h+#`@M zMu;N~DD#RuoMG>_C9T$3eU#r9XB6&m8h}r*BbQ~x98+IDKt!^w_|HXVL3CCXy*hEw z2-zOta@~p{_wRs1dNdefUqBo}e-QNvn@Yvu^>Cl?1{S2O2AI+%($6YAS;^aqq&xY; z@>;&P%4RmRaxC#K&;Z#}3i1Fgn@5S`&m(sa^ne_CZ((g&JLR8=;%#4njLJL@oK+eo&uDN?=nauUzBf=g0c;T;cNF_is)d Gp7{%LmGdzG diff --git a/netbox/project-static/src/buttons.ts b/netbox/project-static/src/buttons.ts deleted file mode 100644 index da3a4aec0..000000000 --- a/netbox/project-static/src/buttons.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { createToast } from './bs'; -import { setColorMode } from './colorMode'; -import { objectDepthState } from './stores'; -import { - slugify, - isTruthy, - apiPatch, - hasError, - getElement, - getElements, - findFirstAdjacent, -} from './util'; - -import type { StateManager } from './state'; - -type ObjectDepthState = { hidden: boolean }; - -/** - * When the toggle button is clicked, swap the connection status via the API and toggle CSS - * classes to reflect the connection status. - * - * @param element Connection Toggle Button Element - */ -function toggleConnection(element: HTMLButtonElement): void { - const id = element.getAttribute('data'); - const connected = element.classList.contains('connected'); - const status = connected ? 'planned' : 'connected'; - - if (isTruthy(id)) { - apiPatch(`/api/dcim/cables/${id}/`, { status }).then(res => { - if (hasError(res)) { - // If the API responds with an error, show it to the user. - createToast('danger', 'Error', res.error).show(); - return; - } else { - // Get the button's row to change its styles. - const row = element.parentElement?.parentElement as HTMLTableRowElement; - // Get the button's icon to change its CSS class. - const icon = element.querySelector('i.mdi, span.mdi') as HTMLSpanElement; - if (connected) { - row.classList.remove('success'); - row.classList.add('info'); - element.classList.remove('connected', 'btn-warning'); - element.classList.add('btn-info'); - element.title = 'Mark Installed'; - icon.classList.remove('mdi-lan-disconnect'); - icon.classList.add('mdi-lan-connect'); - } else { - row.classList.remove('info'); - row.classList.add('success'); - element.classList.remove('btn-success'); - element.classList.add('connected', 'btn-warning'); - element.title = 'Mark Installed'; - icon.classList.remove('mdi-lan-connect'); - icon.classList.add('mdi-lan-disconnect'); - } - } - }); - } -} - -function initConnectionToggle(): void { - for (const element of getElements('button.cable-toggle')) { - element.addEventListener('click', () => toggleConnection(element)); - } -} - -/** - * Change toggle button's text and attribute to reflect the current state. - * - * @param hidden `true` if the current state is hidden, `false` otherwise. - * @param button Toggle element. - */ -function toggleDepthButton(hidden: boolean, button: HTMLButtonElement): void { - button.setAttribute('data-depth-indicators', hidden ? 'hidden' : 'shown'); - button.innerText = hidden ? 'Show Depth Indicators' : 'Hide Depth Indicators'; -} - -/** - * Show all depth indicators. - */ -function showDepthIndicators(): void { - for (const element of getElements('.record-depth')) { - element.style.display = ''; - } -} - -/** - * Hide all depth indicators. - */ -function hideDepthIndicators(): void { - for (const element of getElements('.record-depth')) { - element.style.display = 'none'; - } -} - -/** - * Update object depth local state and visualization when the button is clicked. - * - * @param state State instance. - * @param button Toggle element. - */ -function handleDepthToggle(state: StateManager, button: HTMLButtonElement): void { - const initiallyHidden = state.get('hidden'); - state.set('hidden', !initiallyHidden); - const hidden = state.get('hidden'); - - if (hidden) { - hideDepthIndicators(); - } else { - showDepthIndicators(); - } - toggleDepthButton(hidden, button); -} - -/** - * Initialize object depth toggle buttons. - */ -function initDepthToggle(): void { - const initiallyHidden = objectDepthState.get('hidden'); - - for (const button of getElements('button.toggle-depth')) { - toggleDepthButton(initiallyHidden, button); - - button.addEventListener( - 'click', - event => { - handleDepthToggle(objectDepthState, event.currentTarget as HTMLButtonElement); - }, - false, - ); - } - // Synchronize local state with default DOM elements. - if (initiallyHidden) { - hideDepthIndicators(); - } else if (!initiallyHidden) { - showDepthIndicators(); - } -} - -/** - * If a slug field exists, add event listeners to handle automatically generating its value. - */ -function initReslug(): void { - const slugField = document.getElementById('id_slug') as HTMLInputElement; - const slugButton = document.getElementById('reslug') as HTMLButtonElement; - if (slugField === null || slugButton === null) { - return; - } - const sourceId = slugField.getAttribute('slug-source'); - const sourceField = document.getElementById(`id_${sourceId}`) as HTMLInputElement; - - if (sourceField === null) { - console.error('Unable to find field for slug field.'); - return; - } - - const slugLengthAttr = slugField.getAttribute('maxlength'); - let slugLength = 50; - - if (slugLengthAttr) { - slugLength = Number(slugLengthAttr); - } - sourceField.addEventListener('blur', () => { - slugField.value = slugify(sourceField.value, slugLength); - }); - slugButton.addEventListener('click', () => { - slugField.value = slugify(sourceField.value, slugLength); - }); -} - -/** - * Perform actions in the UI based on the value of user profile updates. - * - * @param event Form Submit - */ -function handlePreferenceSave(event: Event): void { - // Create a FormData instance to access the form values. - const form = event.currentTarget as HTMLFormElement; - const formData = new FormData(form); - - // Update the UI color mode immediately when the user preference changes. - if (formData.get('ui.colormode') === 'dark') { - setColorMode('dark'); - } else if (formData.get('ui.colormode') === 'light') { - setColorMode('light'); - } -} - -/** - * Initialize handlers for user profile updates. - */ -function initPreferenceUpdate(): void { - const form = getElement('preferences-update'); - if (form !== null) { - form.addEventListener('submit', handlePreferenceSave); - } -} - -/** - * Show the select all card when the select all checkbox is checked, and sync the checkbox state - * with all the PK checkboxes in the table. - * - * @param event Change Event - */ -function handleSelectAllToggle(event: Event): void { - // Select all checkbox in header row. - const tableSelectAll = event.currentTarget as HTMLInputElement; - // Nearest table to the select all checkbox. - const table = findFirstAdjacent(tableSelectAll, 'table'); - // Select all confirmation card. - const confirmCard = document.getElementById('select-all-box'); - // Checkbox in confirmation card to signal if all objects should be selected. - const confirmCheckbox = document.getElementById('select-all') as Nullable; - - if (table !== null) { - for (const element of table.querySelectorAll( - 'input[type="checkbox"][name="pk"]', - )) { - if (tableSelectAll.checked) { - // Check all PK checkboxes if the select all checkbox is checked. - element.checked = true; - } else { - // Uncheck all PK checkboxes if the select all checkbox is unchecked. - element.checked = false; - } - } - if (confirmCard !== null) { - if (tableSelectAll.checked) { - // Unhide the select all confirmation card if the select all checkbox is checked. - confirmCard.classList.remove('d-none'); - } else { - // Hide the select all confirmation card if the select all checkbox is unchecked. - confirmCard.classList.add('d-none'); - if (confirmCheckbox !== null) { - // Uncheck the confirmation checkbox when the table checkbox is unchecked (after which - // the confirmation card will be hidden). - confirmCheckbox.checked = false; - } - } - } - } -} - -/** - * If any PK checkbox is checked, uncheck the select all table checkbox and the select all - * confirmation checkbox. - * - * @param event Change Event - */ -function handlePkCheck(event: Event): void { - const target = event.currentTarget as HTMLInputElement; - if (!target.checked) { - for (const element of getElements( - 'input[type="checkbox"].toggle', - 'input#select-all', - )) { - element.checked = false; - } - } -} - -/** - * Synchronize the select all confirmation checkbox state with the select all confirmation button - * disabled state. If the select all confirmation checkbox is checked, the buttons should be - * enabled. If not, the buttons should be disabled. - * - * @param event Change Event - */ -function handleSelectAll(event: Event): void { - const target = event.currentTarget as HTMLInputElement; - const selectAllBox = getElement('select-all-box'); - if (selectAllBox !== null) { - for (const button of selectAllBox.querySelectorAll( - 'button[type="submit"]', - )) { - if (target.checked) { - button.disabled = false; - } else { - button.disabled = true; - } - } - } -} - -/** - * Initialize table select all elements. - */ -function initSelectAll(): void { - for (const element of getElements( - 'table tr th > input[type="checkbox"].toggle', - )) { - element.addEventListener('change', handleSelectAllToggle); - } - for (const element of getElements('input[type="checkbox"][name="pk"]')) { - element.addEventListener('change', handlePkCheck); - } - const selectAll = getElement('select-all'); - - if (selectAll !== null) { - selectAll.addEventListener('change', handleSelectAll); - } -} - -function handlePerPageSelect(event: Event): void { - const select = event.currentTarget as HTMLSelectElement; - if (select.form !== null) { - select.form.submit(); - } -} - -function initPerPage(): void { - for (const element of getElements('select.per-page')) { - element.addEventListener('change', handlePerPageSelect); - } -} - -export function initButtons(): void { - for (const func of [ - initDepthToggle, - initConnectionToggle, - initReslug, - initSelectAll, - initPreferenceUpdate, - initPerPage, - ]) { - func(); - } -} diff --git a/netbox/project-static/src/buttons/connectionToggle.ts b/netbox/project-static/src/buttons/connectionToggle.ts new file mode 100644 index 000000000..6485bbb50 --- /dev/null +++ b/netbox/project-static/src/buttons/connectionToggle.ts @@ -0,0 +1,52 @@ +import { createToast } from '../bs'; +import { isTruthy, apiPatch, hasError, getElements } from '../util'; + +/** + * When the toggle button is clicked, swap the connection status via the API and toggle CSS + * classes to reflect the connection status. + * + * @param element Connection Toggle Button Element + */ +function toggleConnection(element: HTMLButtonElement): void { + const id = element.getAttribute('data'); + const connected = element.classList.contains('connected'); + const status = connected ? 'planned' : 'connected'; + + if (isTruthy(id)) { + apiPatch(`/api/dcim/cables/${id}/`, { status }).then(res => { + if (hasError(res)) { + // If the API responds with an error, show it to the user. + createToast('danger', 'Error', res.error).show(); + return; + } else { + // Get the button's row to change its styles. + const row = element.parentElement?.parentElement as HTMLTableRowElement; + // Get the button's icon to change its CSS class. + const icon = element.querySelector('i.mdi, span.mdi') as HTMLSpanElement; + if (connected) { + row.classList.remove('success'); + row.classList.add('info'); + element.classList.remove('connected', 'btn-warning'); + element.classList.add('btn-info'); + element.title = 'Mark Installed'; + icon.classList.remove('mdi-lan-disconnect'); + icon.classList.add('mdi-lan-connect'); + } else { + row.classList.remove('info'); + row.classList.add('success'); + element.classList.remove('btn-success'); + element.classList.add('connected', 'btn-warning'); + element.title = 'Mark Installed'; + icon.classList.remove('mdi-lan-connect'); + icon.classList.add('mdi-lan-disconnect'); + } + } + }); + } +} + +export function initConnectionToggle(): void { + for (const element of getElements('button.cable-toggle')) { + element.addEventListener('click', () => toggleConnection(element)); + } +} diff --git a/netbox/project-static/src/buttons/depthToggle.ts b/netbox/project-static/src/buttons/depthToggle.ts new file mode 100644 index 000000000..2e17ec02f --- /dev/null +++ b/netbox/project-static/src/buttons/depthToggle.ts @@ -0,0 +1,79 @@ +import { objectDepthState } from '../stores'; +import { getElements } from '../util'; + +import type { StateManager } from '../state'; + +type ObjectDepthState = { hidden: boolean }; + +/** + * Change toggle button's text and attribute to reflect the current state. + * + * @param hidden `true` if the current state is hidden, `false` otherwise. + * @param button Toggle element. + */ +function toggleDepthButton(hidden: boolean, button: HTMLButtonElement): void { + button.setAttribute('data-depth-indicators', hidden ? 'hidden' : 'shown'); + button.innerText = hidden ? 'Show Depth Indicators' : 'Hide Depth Indicators'; +} + +/** + * Show all depth indicators. + */ +function showDepthIndicators(): void { + for (const element of getElements('.record-depth')) { + element.style.display = ''; + } +} + +/** + * Hide all depth indicators. + */ +function hideDepthIndicators(): void { + for (const element of getElements('.record-depth')) { + element.style.display = 'none'; + } +} + +/** + * Update object depth local state and visualization when the button is clicked. + * + * @param state State instance. + * @param button Toggle element. + */ +function handleDepthToggle(state: StateManager, button: HTMLButtonElement): void { + const initiallyHidden = state.get('hidden'); + state.set('hidden', !initiallyHidden); + const hidden = state.get('hidden'); + + if (hidden) { + hideDepthIndicators(); + } else { + showDepthIndicators(); + } + toggleDepthButton(hidden, button); +} + +/** + * Initialize object depth toggle buttons. + */ +export function initDepthToggle(): void { + const initiallyHidden = objectDepthState.get('hidden'); + + for (const button of getElements('button.toggle-depth')) { + toggleDepthButton(initiallyHidden, button); + + button.addEventListener( + 'click', + event => { + handleDepthToggle(objectDepthState, event.currentTarget as HTMLButtonElement); + }, + false, + ); + } + // Synchronize local state with default DOM elements. + if (initiallyHidden) { + hideDepthIndicators(); + } else if (!initiallyHidden) { + showDepthIndicators(); + } +} diff --git a/netbox/project-static/src/buttons/index.ts b/netbox/project-static/src/buttons/index.ts new file mode 100644 index 000000000..1fcc7b87e --- /dev/null +++ b/netbox/project-static/src/buttons/index.ts @@ -0,0 +1,21 @@ +import { initConnectionToggle } from './connectionToggle'; +import { initDepthToggle } from './depthToggle'; +import { initMoveButtons } from './moveOptions'; +import { initPerPage } from './pagination'; +import { initPreferenceUpdate } from './preferences'; +import { initReslug } from './reslug'; +import { initSelectAll } from './selectAll'; + +export function initButtons(): void { + for (const func of [ + initDepthToggle, + initConnectionToggle, + initReslug, + initSelectAll, + initPreferenceUpdate, + initPerPage, + initMoveButtons, + ]) { + func(); + } +} diff --git a/netbox/project-static/src/buttons/moveOptions.ts b/netbox/project-static/src/buttons/moveOptions.ts new file mode 100644 index 000000000..fee36d609 --- /dev/null +++ b/netbox/project-static/src/buttons/moveOptions.ts @@ -0,0 +1,61 @@ +import { getElements } from '../util'; + +/** + * Move selected options of a select element up in order. + * + * Adapted from: + * @see https://www.tomred.net/css-html-js/reorder-option-elements-of-an-html-select.html + * @param element Select Element + */ +function moveOptionUp(element: HTMLSelectElement): void { + const options = Array.from(element.options); + for (let i = 1; i < options.length; i++) { + const option = options[i]; + if (option.selected) { + element.removeChild(option); + element.insertBefore(option, element.options[i - 1]); + } + } +} + +/** + * Move selected options of a select element down in order. + * + * Adapted from: + * @see https://www.tomred.net/css-html-js/reorder-option-elements-of-an-html-select.html + * @param element Select Element + */ +function moveOptionDown(element: HTMLSelectElement): void { + const options = Array.from(element.options); + for (let i = options.length - 2; i >= 0; i--) { + let option = options[i]; + if (option.selected) { + let next = element.options[i + 1]; + option = element.removeChild(option); + next = element.replaceChild(option, next); + element.insertBefore(next, option); + } + } +} + +/** + * Initialize move up/down buttons. + */ +export function initMoveButtons(): void { + for (const button of getElements('#move-option-up')) { + const target = button.getAttribute('data-target'); + if (target !== null) { + for (const select of getElements(`#${target}`)) { + button.addEventListener('click', () => moveOptionUp(select)); + } + } + } + for (const button of getElements('#move-option-down')) { + const target = button.getAttribute('data-target'); + if (target !== null) { + for (const select of getElements(`#${target}`)) { + button.addEventListener('click', () => moveOptionDown(select)); + } + } + } +} diff --git a/netbox/project-static/src/buttons/pagination.ts b/netbox/project-static/src/buttons/pagination.ts new file mode 100644 index 000000000..670dc7390 --- /dev/null +++ b/netbox/project-static/src/buttons/pagination.ts @@ -0,0 +1,14 @@ +import { getElements } from '../util'; + +function handlePerPageSelect(event: Event): void { + const select = event.currentTarget as HTMLSelectElement; + if (select.form !== null) { + select.form.submit(); + } +} + +export function initPerPage(): void { + for (const element of getElements('select.per-page')) { + element.addEventListener('change', handlePerPageSelect); + } +} diff --git a/netbox/project-static/src/buttons/preferences.ts b/netbox/project-static/src/buttons/preferences.ts new file mode 100644 index 000000000..6e8b21c02 --- /dev/null +++ b/netbox/project-static/src/buttons/preferences.ts @@ -0,0 +1,30 @@ +import { setColorMode } from '../colorMode'; +import { getElement } from '../util'; + +/** + * Perform actions in the UI based on the value of user profile updates. + * + * @param event Form Submit + */ +function handlePreferenceSave(event: Event): void { + // Create a FormData instance to access the form values. + const form = event.currentTarget as HTMLFormElement; + const formData = new FormData(form); + + // Update the UI color mode immediately when the user preference changes. + if (formData.get('ui.colormode') === 'dark') { + setColorMode('dark'); + } else if (formData.get('ui.colormode') === 'light') { + setColorMode('light'); + } +} + +/** + * Initialize handlers for user profile updates. + */ +export function initPreferenceUpdate(): void { + const form = getElement('preferences-update'); + if (form !== null) { + form.addEventListener('submit', handlePreferenceSave); + } +} diff --git a/netbox/project-static/src/buttons/reslug.ts b/netbox/project-static/src/buttons/reslug.ts new file mode 100644 index 000000000..2549bf112 --- /dev/null +++ b/netbox/project-static/src/buttons/reslug.ts @@ -0,0 +1,46 @@ +/** + * Create a slug from any input string. + * + * @param slug Original string. + * @param chars Maximum number of characters. + * @returns Slugified string. + */ +function slugify(slug: string, chars: number): string { + return slug + .replace(/[^\-.\w\s]/g, '') // Remove unneeded chars + .replace(/^[\s.]+|[\s.]+$/g, '') // Trim leading/trailing spaces + .replace(/[-.\s]+/g, '-') // Convert spaces and decimals to hyphens + .toLowerCase() // Convert to lowercase + .substring(0, chars); // Trim to first chars chars +} + +/** + * If a slug field exists, add event listeners to handle automatically generating its value. + */ +export function initReslug(): void { + const slugField = document.getElementById('id_slug') as HTMLInputElement; + const slugButton = document.getElementById('reslug') as HTMLButtonElement; + if (slugField === null || slugButton === null) { + return; + } + const sourceId = slugField.getAttribute('slug-source'); + const sourceField = document.getElementById(`id_${sourceId}`) as HTMLInputElement; + + if (sourceField === null) { + console.error('Unable to find field for slug field.'); + return; + } + + const slugLengthAttr = slugField.getAttribute('maxlength'); + let slugLength = 50; + + if (slugLengthAttr) { + slugLength = Number(slugLengthAttr); + } + sourceField.addEventListener('blur', () => { + slugField.value = slugify(sourceField.value, slugLength); + }); + slugButton.addEventListener('click', () => { + slugField.value = slugify(sourceField.value, slugLength); + }); +} diff --git a/netbox/project-static/src/buttons/selectAll.ts b/netbox/project-static/src/buttons/selectAll.ts new file mode 100644 index 000000000..8b62ef0a0 --- /dev/null +++ b/netbox/project-static/src/buttons/selectAll.ts @@ -0,0 +1,106 @@ +import { getElement, getElements, findFirstAdjacent } from '../util'; + +/** + * If any PK checkbox is checked, uncheck the select all table checkbox and the select all + * confirmation checkbox. + * + * @param event Change Event + */ +function handlePkCheck(event: Event): void { + const target = event.currentTarget as HTMLInputElement; + if (!target.checked) { + for (const element of getElements( + 'input[type="checkbox"].toggle', + 'input#select-all', + )) { + element.checked = false; + } + } +} + +/** + * Show the select all card when the select all checkbox is checked, and sync the checkbox state + * with all the PK checkboxes in the table. + * + * @param event Change Event + */ +function handleSelectAllToggle(event: Event): void { + // Select all checkbox in header row. + const tableSelectAll = event.currentTarget as HTMLInputElement; + // Nearest table to the select all checkbox. + const table = findFirstAdjacent(tableSelectAll, 'table'); + // Select all confirmation card. + const confirmCard = document.getElementById('select-all-box'); + // Checkbox in confirmation card to signal if all objects should be selected. + const confirmCheckbox = document.getElementById('select-all') as Nullable; + + if (table !== null) { + for (const element of table.querySelectorAll( + 'input[type="checkbox"][name="pk"]', + )) { + if (tableSelectAll.checked) { + // Check all PK checkboxes if the select all checkbox is checked. + element.checked = true; + } else { + // Uncheck all PK checkboxes if the select all checkbox is unchecked. + element.checked = false; + } + } + if (confirmCard !== null) { + if (tableSelectAll.checked) { + // Unhide the select all confirmation card if the select all checkbox is checked. + confirmCard.classList.remove('d-none'); + } else { + // Hide the select all confirmation card if the select all checkbox is unchecked. + confirmCard.classList.add('d-none'); + if (confirmCheckbox !== null) { + // Uncheck the confirmation checkbox when the table checkbox is unchecked (after which + // the confirmation card will be hidden). + confirmCheckbox.checked = false; + } + } + } + } +} + +/** + * Synchronize the select all confirmation checkbox state with the select all confirmation button + * disabled state. If the select all confirmation checkbox is checked, the buttons should be + * enabled. If not, the buttons should be disabled. + * + * @param event Change Event + */ +function handleSelectAll(event: Event): void { + const target = event.currentTarget as HTMLInputElement; + const selectAllBox = getElement('select-all-box'); + if (selectAllBox !== null) { + for (const button of selectAllBox.querySelectorAll( + 'button[type="submit"]', + )) { + if (target.checked) { + button.disabled = false; + } else { + button.disabled = true; + } + } + } +} + +/** + * Initialize table select all elements. + */ +export function initSelectAll(): void { + for (const element of getElements( + 'table tr th > input[type="checkbox"].toggle', + )) { + element.addEventListener('change', handleSelectAllToggle); + } + for (const element of getElements('input[type="checkbox"][name="pk"]')) { + element.addEventListener('change', handlePkCheck); + } + const selectAll = getElement('select-all'); + + if (selectAll !== null) { + selectAll.addEventListener('change', handleSelectAll); + } +} diff --git a/netbox/project-static/src/forms.ts b/netbox/project-static/src/forms.ts deleted file mode 100644 index 1dfd20cb5..000000000 --- a/netbox/project-static/src/forms.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { getElements, scrollTo, findFirstAdjacent, isTruthy } from './util'; - -type ShowHideMap = { - default: { hide: string[]; show: string[] }; - [k: string]: { hide: string[]; show: string[] }; -}; - -/** - * Handle bulk add/edit/rename form actions. - * - * @param event Click Event - */ -function handleFormActionClick(event: Event): void { - event.preventDefault(); - const element = event.currentTarget as HTMLElement; - if (element !== null) { - const form = findFirstAdjacent(element, 'form'); - const href = element.getAttribute('href'); - if (form !== null && isTruthy(href)) { - form.setAttribute('action', href); - form.submit(); - } - } -} - -/** - * Initialize bulk form action links. - */ -function initFormActions() { - for (const element of getElements('a.formaction')) { - element.addEventListener('click', handleFormActionClick); - } -} - -/** - * Get form data from a form element and transform it into a body usable by fetch. - * - * @param element Form element - * @returns Fetch body - */ -export function getFormData(element: HTMLFormElement): URLSearchParams { - const formData = new FormData(element); - const body = new URLSearchParams(); - for (const [k, v] of formData) { - body.append(k, v as string); - } - return body; -} - -/** - * Set the value of the number input field based on the selection of the dropdown. - */ -function initSpeedSelector(): void { - for (const element of getElements('a.set_speed')) { - if (element !== null) { - function handleClick(event: Event) { - // Don't reload the page (due to href="#"). - event.preventDefault(); - // Get the value of the `data` attribute on the dropdown option. - const value = element.getAttribute('data'); - // Find the input element referenced by the dropdown element. - const input = document.getElementById(element.target) as Nullable; - if (input !== null && value !== null) { - // Set the value of the input field to the `data` attribute's value. - input.value = value; - } - } - element.addEventListener('click', handleClick); - } - } -} - -function handleFormSubmit(event: Event, form: HTMLFormElement): void { - // Track the names of each invalid field. - const invalids = new Set(); - - for (const element of form.querySelectorAll('*[name]')) { - if (!element.validity.valid) { - invalids.add(element.name); - - // If the field is invalid, but contains the .is-valid class, remove it. - if (element.classList.contains('is-valid')) { - element.classList.remove('is-valid'); - } - // If the field is invalid, but doesn't contain the .is-invalid class, add it. - if (!element.classList.contains('is-invalid')) { - element.classList.add('is-invalid'); - } - } else { - // If the field is valid, but contains the .is-invalid class, remove it. - if (element.classList.contains('is-invalid')) { - element.classList.remove('is-invalid'); - } - // If the field is valid, but doesn't contain the .is-valid class, add it. - if (!element.classList.contains('is-valid')) { - element.classList.add('is-valid'); - } - } - } - - if (invalids.size !== 0) { - // If there are invalid fields, pick the first field and scroll to it. - const firstInvalid = form.elements.namedItem(Array.from(invalids)[0]) as Element; - scrollTo(firstInvalid); - - // If the form has invalid fields, don't submit it. - event.preventDefault(); - } -} - -/** - * Attach an event listener to each form's submitter (button[type=submit]). When called, the - * callback checks the validity of each form field and adds the appropriate Bootstrap CSS class - * based on the field's validity. - */ -function initFormElements() { - for (const form of getElements('form')) { - // Find each of the form's submitters. Most object edit forms have a "Create" and - // a "Create & Add", so we need to add a listener to both. - const submitters = form.querySelectorAll('button[type=submit]'); - - for (const submitter of submitters) { - // Add the event listener to each submitter. - submitter.addEventListener('click', event => handleFormSubmit(event, form)); - } - } -} - -/** - * Move selected options of a select element up in order. - * - * Adapted from: - * @see https://www.tomred.net/css-html-js/reorder-option-elements-of-an-html-select.html - * @param element Select Element - */ -function moveOptionUp(element: HTMLSelectElement): void { - const options = Array.from(element.options); - for (let i = 1; i < options.length; i++) { - const option = options[i]; - if (option.selected) { - element.removeChild(option); - element.insertBefore(option, element.options[i - 1]); - } - } -} - -/** - * Move selected options of a select element down in order. - * - * Adapted from: - * @see https://www.tomred.net/css-html-js/reorder-option-elements-of-an-html-select.html - * @param element Select Element - */ -function moveOptionDown(element: HTMLSelectElement): void { - const options = Array.from(element.options); - for (let i = options.length - 2; i >= 0; i--) { - let option = options[i]; - if (option.selected) { - let next = element.options[i + 1]; - option = element.removeChild(option); - next = element.replaceChild(option, next); - element.insertBefore(next, option); - } - } -} - -/** - * Initialize move up/down buttons. - */ -function initMoveButtons() { - for (const button of getElements('#move-option-up')) { - const target = button.getAttribute('data-target'); - if (target !== null) { - for (const select of getElements(`#${target}`)) { - button.addEventListener('click', () => moveOptionUp(select)); - } - } - } - for (const button of getElements('#move-option-down')) { - const target = button.getAttribute('data-target'); - if (target !== null) { - for (const select of getElements(`#${target}`)) { - button.addEventListener('click', () => moveOptionDown(select)); - } - } - } -} - -/** - * Mapping of scope names to arrays of object types whose fields should be hidden or shown when - * the scope type (key) is selected. - * - * For example, if `region` is the scope type, the fields with IDs listed in - * showHideMap.region.hide should be hidden, and the fields with IDs listed in - * showHideMap.region.show should be shown. - */ -const showHideMap: ShowHideMap = { - region: { - hide: ['id_sitegroup', 'id_site', 'id_location', 'id_rack', 'id_clustergroup', 'id_cluster'], - show: ['id_region'], - }, - 'site group': { - hide: ['id_region', 'id_site', 'id_location', 'id_rack', 'id_clustergroup', 'id_cluster'], - show: ['id_sitegroup'], - }, - site: { - hide: ['id_location', 'id_rack', 'id_clustergroup', 'id_cluster'], - show: ['id_region', 'id_sitegroup', 'id_site'], - }, - location: { - hide: ['id_rack', 'id_clustergroup', 'id_cluster'], - show: ['id_region', 'id_sitegroup', 'id_site', 'id_location'], - }, - rack: { - hide: ['id_clustergroup', 'id_cluster'], - show: ['id_region', 'id_sitegroup', 'id_site', 'id_location', 'id_rack'], - }, - 'cluster group': { - hide: ['id_region', 'id_sitegroup', 'id_site', 'id_location', 'id_rack', 'id_cluster'], - show: ['id_clustergroup'], - }, - cluster: { - hide: ['id_region', 'id_sitegroup', 'id_site', 'id_location', 'id_rack'], - show: ['id_clustergroup', 'id_cluster'], - }, - default: { - hide: [ - 'id_region', - 'id_sitegroup', - 'id_site', - 'id_location', - 'id_rack', - 'id_clustergroup', - 'id_cluster', - ], - show: [], - }, -}; - -/** - * Toggle visibility of a given element's parent. - * @param query CSS Query. - * @param action Show or Hide the Parent. - */ -function toggleParentVisibility(query: string, action: 'show' | 'hide') { - for (const element of getElements(query)) { - if (action === 'show') { - element.parentElement?.classList.remove('d-none', 'invisible'); - } else { - element.parentElement?.classList.add('d-none', 'invisible'); - } - } -} - -/** - * Handle changes to the Scope Type field. - */ -function handleScopeChange(event: Event) { - const element = event.currentTarget as HTMLSelectElement; - // Scope type's innerText looks something like `DCIM > region`. - const scopeType = element.options[element.selectedIndex].innerText.toLowerCase(); - - for (const [scope, fields] of Object.entries(showHideMap)) { - // If the scope type ends with the specified scope, toggle its field visibility according to - // the show/hide values. - if (scopeType.endsWith(scope)) { - for (const field of fields.hide) { - toggleParentVisibility(`#${field}`, 'hide'); - } - for (const field of fields.show) { - toggleParentVisibility(`#${field}`, 'show'); - } - // Stop on first match. - break; - } else { - // Otherwise, hide all fields. - for (const field of showHideMap.default.hide) { - toggleParentVisibility(`#${field}`, 'hide'); - } - } - } -} - -/** - * Initialize scope type select event listeners. - */ -function initScopeSelector() { - for (const element of getElements('#id_scope_type')) { - element.addEventListener('change', handleScopeChange); - } -} - -export function initForms(): void { - for (const func of [ - initFormElements, - initFormActions, - initMoveButtons, - initSpeedSelector, - initScopeSelector, - ]) { - func(); - } -} diff --git a/netbox/project-static/src/forms/actions.ts b/netbox/project-static/src/forms/actions.ts new file mode 100644 index 000000000..a83521d0d --- /dev/null +++ b/netbox/project-static/src/forms/actions.ts @@ -0,0 +1,28 @@ +import { getElements, findFirstAdjacent, isTruthy } from '../util'; + +/** + * Handle bulk add/edit/rename form actions. + * + * @param event Click Event + */ +function handleFormActionClick(event: Event): void { + event.preventDefault(); + const element = event.currentTarget as HTMLElement; + if (element !== null) { + const form = findFirstAdjacent(element, 'form'); + const href = element.getAttribute('href'); + if (form !== null && isTruthy(href)) { + form.setAttribute('action', href); + form.submit(); + } + } +} + +/** + * Initialize bulk form action links. + */ +export function initFormActions(): void { + for (const element of getElements('a.formaction')) { + element.addEventListener('click', handleFormActionClick); + } +} diff --git a/netbox/project-static/src/forms/elements.ts b/netbox/project-static/src/forms/elements.ts new file mode 100644 index 000000000..978c25e10 --- /dev/null +++ b/netbox/project-static/src/forms/elements.ts @@ -0,0 +1,57 @@ +import { getElements, scrollTo } from '../util'; + +function handleFormSubmit(event: Event, form: HTMLFormElement): void { + // Track the names of each invalid field. + const invalids = new Set(); + + for (const element of form.querySelectorAll('*[name]')) { + if (!element.validity.valid) { + invalids.add(element.name); + + // If the field is invalid, but contains the .is-valid class, remove it. + if (element.classList.contains('is-valid')) { + element.classList.remove('is-valid'); + } + // If the field is invalid, but doesn't contain the .is-invalid class, add it. + if (!element.classList.contains('is-invalid')) { + element.classList.add('is-invalid'); + } + } else { + // If the field is valid, but contains the .is-invalid class, remove it. + if (element.classList.contains('is-invalid')) { + element.classList.remove('is-invalid'); + } + // If the field is valid, but doesn't contain the .is-valid class, add it. + if (!element.classList.contains('is-valid')) { + element.classList.add('is-valid'); + } + } + } + + if (invalids.size !== 0) { + // If there are invalid fields, pick the first field and scroll to it. + const firstInvalid = form.elements.namedItem(Array.from(invalids)[0]) as Element; + scrollTo(firstInvalid); + + // If the form has invalid fields, don't submit it. + event.preventDefault(); + } +} + +/** + * Attach an event listener to each form's submitter (button[type=submit]). When called, the + * callback checks the validity of each form field and adds the appropriate Bootstrap CSS class + * based on the field's validity. + */ +export function initFormElements(): void { + for (const form of getElements('form')) { + // Find each of the form's submitters. Most object edit forms have a "Create" and + // a "Create & Add", so we need to add a listener to both. + const submitters = form.querySelectorAll('button[type=submit]'); + + for (const submitter of submitters) { + // Add the event listener to each submitter. + submitter.addEventListener('click', (event: Event) => handleFormSubmit(event, form)); + } + } +} diff --git a/netbox/project-static/src/forms/index.ts b/netbox/project-static/src/forms/index.ts new file mode 100644 index 000000000..2c409dd76 --- /dev/null +++ b/netbox/project-static/src/forms/index.ts @@ -0,0 +1,17 @@ +import { initFormActions } from './actions'; +import { initFormElements } from './elements'; +import { initSpeedSelector } from './speedSelector'; +import { initScopeSelector } from './scopeSelector'; +import { initVlanTags } from './vlanTags'; + +export function initForms(): void { + for (const func of [ + initFormActions, + initFormElements, + initSpeedSelector, + initScopeSelector, + initVlanTags, + ]) { + func(); + } +} diff --git a/netbox/project-static/src/forms/scopeSelector.ts b/netbox/project-static/src/forms/scopeSelector.ts new file mode 100644 index 000000000..ad107f9b3 --- /dev/null +++ b/netbox/project-static/src/forms/scopeSelector.ts @@ -0,0 +1,109 @@ +import { getElements } from '../util'; + +type ShowHideMap = { + default: { hide: string[]; show: string[] }; + [k: string]: { hide: string[]; show: string[] }; +}; + +/** + * Mapping of scope names to arrays of object types whose fields should be hidden or shown when + * the scope type (key) is selected. + * + * For example, if `region` is the scope type, the fields with IDs listed in + * showHideMap.region.hide should be hidden, and the fields with IDs listed in + * showHideMap.region.show should be shown. + */ +const showHideMap: ShowHideMap = { + region: { + hide: ['id_sitegroup', 'id_site', 'id_location', 'id_rack', 'id_clustergroup', 'id_cluster'], + show: ['id_region'], + }, + 'site group': { + hide: ['id_region', 'id_site', 'id_location', 'id_rack', 'id_clustergroup', 'id_cluster'], + show: ['id_sitegroup'], + }, + site: { + hide: ['id_location', 'id_rack', 'id_clustergroup', 'id_cluster'], + show: ['id_region', 'id_sitegroup', 'id_site'], + }, + location: { + hide: ['id_rack', 'id_clustergroup', 'id_cluster'], + show: ['id_region', 'id_sitegroup', 'id_site', 'id_location'], + }, + rack: { + hide: ['id_clustergroup', 'id_cluster'], + show: ['id_region', 'id_sitegroup', 'id_site', 'id_location', 'id_rack'], + }, + 'cluster group': { + hide: ['id_region', 'id_sitegroup', 'id_site', 'id_location', 'id_rack', 'id_cluster'], + show: ['id_clustergroup'], + }, + cluster: { + hide: ['id_region', 'id_sitegroup', 'id_site', 'id_location', 'id_rack'], + show: ['id_clustergroup', 'id_cluster'], + }, + default: { + hide: [ + 'id_region', + 'id_sitegroup', + 'id_site', + 'id_location', + 'id_rack', + 'id_clustergroup', + 'id_cluster', + ], + show: [], + }, +}; +/** + * Toggle visibility of a given element's parent. + * @param query CSS Query. + * @param action Show or Hide the Parent. + */ +function toggleParentVisibility(query: string, action: 'show' | 'hide') { + for (const element of getElements(query)) { + if (action === 'show') { + element.parentElement?.classList.remove('d-none', 'invisible'); + } else { + element.parentElement?.classList.add('d-none', 'invisible'); + } + } +} + +/** + * Handle changes to the Scope Type field. + */ +function handleScopeChange(event: Event) { + const element = event.currentTarget as HTMLSelectElement; + // Scope type's innerText looks something like `DCIM > region`. + const scopeType = element.options[element.selectedIndex].innerText.toLowerCase(); + + for (const [scope, fields] of Object.entries(showHideMap)) { + // If the scope type ends with the specified scope, toggle its field visibility according to + // the show/hide values. + if (scopeType.endsWith(scope)) { + for (const field of fields.hide) { + toggleParentVisibility(`#${field}`, 'hide'); + } + for (const field of fields.show) { + toggleParentVisibility(`#${field}`, 'show'); + } + // Stop on first match. + break; + } else { + // Otherwise, hide all fields. + for (const field of showHideMap.default.hide) { + toggleParentVisibility(`#${field}`, 'hide'); + } + } + } +} + +/** + * Initialize scope type select event listeners. + */ +export function initScopeSelector(): void { + for (const element of getElements('#id_scope_type')) { + element.addEventListener('change', handleScopeChange); + } +} diff --git a/netbox/project-static/src/forms/speedSelector.ts b/netbox/project-static/src/forms/speedSelector.ts new file mode 100644 index 000000000..9195afce3 --- /dev/null +++ b/netbox/project-static/src/forms/speedSelector.ts @@ -0,0 +1,24 @@ +import { getElements } from '../util'; + +/** + * Set the value of the number input field based on the selection of the dropdown. + */ +export function initSpeedSelector(): void { + for (const element of getElements('a.set_speed')) { + if (element !== null) { + function handleClick(event: Event) { + // Don't reload the page (due to href="#"). + event.preventDefault(); + // Get the value of the `data` attribute on the dropdown option. + const value = element.getAttribute('data'); + // Find the input element referenced by the dropdown element. + const input = document.getElementById(element.target) as Nullable; + if (input !== null && value !== null) { + // Set the value of the input field to the `data` attribute's value. + input.value = value; + } + } + element.addEventListener('click', handleClick); + } + } +} diff --git a/netbox/project-static/src/forms/vlanTags.ts b/netbox/project-static/src/forms/vlanTags.ts new file mode 100644 index 000000000..03ec73e60 --- /dev/null +++ b/netbox/project-static/src/forms/vlanTags.ts @@ -0,0 +1,116 @@ +import { all, getElement, resetSelect, toggleVisibility } from '../util'; + +/** + * Get a select element's containing `.row` element. + * + * @param element Select element. + * @returns Containing row element. + */ +function fieldContainer(element: Nullable): Nullable { + const container = element?.parentElement?.parentElement ?? null; + if (container !== null && container.classList.contains('row')) { + return container; + } + return null; +} + +/** + * Toggle element visibility when the mode field does not have a value. + */ +function handleModeNone(): void { + const elements = [ + getElement('id_tagged_vlans'), + getElement('id_untagged_vlan'), + getElement('id_vlan_group'), + ]; + + if (all(elements)) { + const [taggedVlans, untaggedVlan] = elements; + resetSelect(untaggedVlan); + resetSelect(taggedVlans); + for (const element of elements) { + toggleVisibility(fieldContainer(element), 'hide'); + } + } +} + +/** + * Toggle element visibility when the mode field's value is Access. + */ +function handleModeAccess(): void { + const elements = [ + getElement('id_tagged_vlans'), + getElement('id_untagged_vlan'), + getElement('id_vlan_group'), + ]; + if (all(elements)) { + const [taggedVlans, untaggedVlan, vlanGroup] = elements; + resetSelect(taggedVlans); + toggleVisibility(fieldContainer(vlanGroup), 'show'); + toggleVisibility(fieldContainer(untaggedVlan), 'show'); + toggleVisibility(fieldContainer(taggedVlans), 'hide'); + } +} + +/** + * Toggle element visibility when the mode field's value is Tagged. + */ +function handleModeTagged(): void { + const elements = [ + getElement('id_tagged_vlans'), + getElement('id_untagged_vlan'), + getElement('id_vlan_group'), + ]; + if (all(elements)) { + const [taggedVlans, untaggedVlan, vlanGroup] = elements; + toggleVisibility(fieldContainer(taggedVlans), 'show'); + toggleVisibility(fieldContainer(vlanGroup), 'show'); + toggleVisibility(fieldContainer(untaggedVlan), 'show'); + } +} + +/** + * Toggle element visibility when the mode field's value is Tagged (All). + */ +function handleModeTaggedAll(): void { + const elements = [ + getElement('id_tagged_vlans'), + getElement('id_untagged_vlan'), + getElement('id_vlan_group'), + ]; + if (all(elements)) { + const [taggedVlans, untaggedVlan, vlanGroup] = elements; + resetSelect(taggedVlans); + toggleVisibility(fieldContainer(vlanGroup), 'show'); + toggleVisibility(fieldContainer(untaggedVlan), 'show'); + toggleVisibility(fieldContainer(taggedVlans), 'hide'); + } +} + +/** + * Reset field visibility when the mode field's value changes. + */ +function handleModeChange(element: HTMLSelectElement): void { + switch (element.value) { + case 'access': + handleModeAccess(); + break; + case 'tagged': + handleModeTagged(); + break; + case 'tagged-all': + handleModeTaggedAll(); + break; + case '': + handleModeNone(); + break; + } +} + +export function initVlanTags(): void { + const element = getElement('id_mode'); + if (element !== null) { + element.addEventListener('change', () => handleModeChange(element)); + handleModeChange(element); + } +} diff --git a/netbox/project-static/src/util.ts b/netbox/project-static/src/util.ts index 3f399b1c2..50211ed7b 100644 --- a/netbox/project-static/src/util.ts +++ b/netbox/project-static/src/util.ts @@ -26,22 +26,6 @@ export function hasMore(data: APIAnswer): data is APIAnswerWithNe return typeof data.next === 'string'; } -/** - * Create a slug from any input string. - * - * @param slug Original string. - * @param chars Maximum number of characters. - * @returns Slugified string. - */ -export function slugify(slug: string, chars: number): string { - return slug - .replace(/[^\-.\w\s]/g, '') // Remove unneeded chars - .replace(/^[\s.]+|[\s.]+$/g, '') // Trim leading/trailing spaces - .replace(/[-.\s]+/g, '-') // Convert spaces and decimals to hyphens - .toLowerCase() // Convert to lowercase - .substring(0, chars); // Trim to first chars chars -} - /** * Type guard to determine if a value is not null, undefined, or empty. */ @@ -59,6 +43,45 @@ export function isTruthy(value: V): value is NonNullable { return false; } +/** + * Type guard to determine if all elements of an array are not null or undefined. + * + * @example + * ```js + * const elements = [document.getElementById("element1"), document.getElementById("element2")]; + * if (all(elements)) { + * const [element1, element2] = elements; + * // element1 and element2 are now of type HTMLElement, not Nullable. + * } + * ``` + */ +export function all(values: T[]): values is NonNullable[] { + return values.every(value => typeof value !== 'undefined' && value !== null); +} + +/** + * Deselect all selected options and reset the field value of a select element. + * + * @example + * ```js + * const select = document.querySelectorAll("select.example"); + * select.value = "test"; + * console.log(select.value); + * // test + * resetSelect(select); + * console.log(select.value); + * // '' + * ``` + */ +export function resetSelect(select: S): void { + for (const option of select.options) { + if (option.selected) { + option.selected = false; + } + } + select.value = ''; +} + /** * Type guard to determine if a value is an `Element`. */ @@ -245,16 +268,38 @@ export function getNetboxData(key: string): string | null { return null; } +/** + * Toggle visibility of an element. + */ +export function toggleVisibility( + element: E | null, + action?: 'show' | 'hide', +): void { + if (element !== null) { + if (typeof action === 'undefined') { + // No action is passed, so we should toggle the existing state. + const current = window.getComputedStyle(element).display; + if (current === 'none') { + element.style.display = ''; + } else { + element.style.display = 'none'; + } + } else { + if (action === 'show') { + element.style.display = ''; + } else { + element.style.display = 'none'; + } + } + } +} + /** * Toggle visibility of card loader. */ export function toggleLoader(action: 'show' | 'hide'): void { for (const element of getElements('div.card-overlay')) { - if (action === 'show') { - element.classList.remove('d-none'); - } else { - element.classList.add('d-none'); - } + toggleVisibility(element, action); } }