From f542bc70a196a8ab538ee1d3dcad56ac38f3a89f Mon Sep 17 00:00:00 2001 From: henrygd Date: Mon, 8 Sep 2025 13:03:07 -0400 Subject: [PATCH] add biome and apply selected formatting / fixes --- src/site/biome.json | 38 ++++ src/site/bun.lockb | Bin 225313 -> 228807 bytes src/site/package.json | 7 +- src/site/src/components/routes/home.tsx | 27 +-- src/site/src/components/routes/system.tsx | 184 ++++++++++--------- src/site/src/components/ui/alert-dialog.tsx | 5 +- src/site/src/components/ui/badge.tsx | 2 +- src/site/src/components/ui/button.tsx | 2 +- src/site/src/components/ui/chart.tsx | 10 +- src/site/src/components/ui/checkbox.tsx | 2 +- src/site/src/components/ui/collapsible.tsx | 20 +- src/site/src/components/ui/command.tsx | 5 +- src/site/src/components/ui/dialog.tsx | 2 +- src/site/src/components/ui/dropdown-menu.tsx | 2 +- src/site/src/components/ui/icons.tsx | 2 +- src/site/src/components/ui/input-copy.tsx | 6 +- src/site/src/components/ui/input-tags.tsx | 4 +- src/site/src/components/ui/input.tsx | 2 +- src/site/src/components/ui/label.tsx | 2 +- src/site/src/components/ui/otp.tsx | 2 +- src/site/src/components/ui/select.tsx | 5 +- src/site/src/components/ui/separator.tsx | 2 +- src/site/src/components/ui/sheet.tsx | 2 +- src/site/src/components/ui/slider.tsx | 2 +- src/site/src/components/ui/switch.tsx | 2 +- src/site/src/components/ui/tabs.tsx | 2 +- src/site/src/components/ui/toast.tsx | 2 +- src/site/src/components/ui/toaster.tsx | 22 +-- src/site/src/components/ui/tooltip.tsx | 2 +- src/site/src/components/ui/use-toast.ts | 2 +- src/site/src/lib/alerts.ts | 14 +- src/site/src/lib/api.ts | 10 +- src/site/src/lib/i18n.ts | 10 +- src/site/src/lib/stores.ts | 6 +- src/site/src/lib/systemsManager.ts | 14 +- src/site/src/lib/utils.ts | 24 ++- src/site/src/main.tsx | 34 ++-- 37 files changed, 264 insertions(+), 215 deletions(-) create mode 100644 src/site/biome.json diff --git a/src/site/biome.json b/src/site/biome.json new file mode 100644 index 00000000..330e804f --- /dev/null +++ b/src/site/biome.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 2, + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "asNeeded", + "trailingCommas": "es5" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} \ No newline at end of file diff --git a/src/site/bun.lockb b/src/site/bun.lockb index 19985005c1fa2a9b1c51c94364be876871ffa1ca..06672e782e70fc22e19337cb814032836aa6256b 100755 GIT binary patch delta 36225 zcmeIb34Bh+_dkB`l?RuSL=X`Kv4-r+lgRRjy|&mw?IMDZ2og(5{ zC1OuoRTR}lm0DYEDMhvQi59>2xidFUX!w5W^ZEW?umA7ozMSXGIdkUBnVB8K3+BG|6qnPg&sDCg6v(F1>?OG)cJfv*Eo zt=|Gu55*=98cscZtb!!Dfc65vBJgt1)qrz>@z2^F%v!+HWp3?Vw7s584D2cBb`UNr zrAyY7WcUhNavTimpHN`x?|Q(Hwf zq8`Z*`G#SJK_`(9_mZqe3viEniAm!PSl zTwrvL^=d<2ficD-!Ka?-222$;113WaU-AV_2G^0L?z*c+H0I8=8<-S(c=9f<1Wa^d zQW6G8Ds*9ID1bLx6=2H0i#)2}8ZhV-pp$`X0!IO(!>zs|zdW!z=v%(r-%bL<6RcYVo(BwlZLKLt zP{q+ABSg?OfXR^^fvHg=laf;~XQhZxP6q(fOdJt2A}M~5B(2fTn4Ht6Meru406QUf z05A=5N5O9*=sLhOc8xtfQZNRXTGCh0uLG087GSE_ADANB2~^pEnbbd83=78Y{d>np87T4&=3-r-4O1b^tGeD!05)#IYl%#()fiOVl zy7QUxAI_YAd*bXx|C2|3-5*N~1=gKN(A1nSa1wBB&>ewkN_z@i4!9=h+x<8`1?&cT z8*mNaUt+mu90w-PFjkCxps|=*w*r%=oChY)T7YupsNfhR$R&qq8&z}q(!qQRZG@fV zH;F^J+RMNckNd^*K+qbP9QG6N2@e>?<@$owtB)BOGcX}evK!9*t~2;ly;1%v_#@F# z@LZYz0l)|_1Fg<}6p=;(Q;6&dOoj}LPl0KY6ak>- z8B;ZROzddd0=a4VWt`LRC3CYV}2D<$yEC^YR0LF;CO2!Kr-0dV=v91df3w^A7^kJiP}# z;rHL<3gih~{uxDo(-Sh08}h84mN1MA>Xk-%;rLN{0uf|rd@ROBicgLk9|wFDeDar_ zlX%4=V^W5C4v$MUp=YQEM#m*5C5(-ePN#GKI|59RCl{D{U}#Ko(zp?j&EVzdjphy8 z0-Ex%YQ!a{!wDxNgBZqAVk{j=;5!xJKfA;>0-bm=URoF)4H_F+?x;2({73 zHv&cd%I5Hj?B??R8YA!#f$isUJ%$Gk75u)y)X<3eG$G+Z^^qW#RTl6W=n9%#c@t== z2qq*9N2MVeHxQoTIXEF^NH-{=o=S)t8#e;^1u%#dPg%(Msle1yr$v0G$BiDHG}<$6 z{Bh7ErvN*`+pIkn^OkJ{rjYr(3S$``d80+o&Z%L^p1L!k$417CPL4~20o0IBmU6rg zm?m-eW!ykGgOpB|q%G%`9v8U!3Le~3Mh65zMjAP0L~3l(pg0HcNx>kjgBF4DSQ<-)EgL;Slxk1z6vnaS00#pCM!!SsSEVdHrksVPF0cz)PkbPK z%+~DYEtv*P{h0(@1-K`0W#Fd3C}V91Oq0$LnDYPJ#q+QI<@9F8jz^Bkj&0VgNZ2sy z!+Kky=Qf?ZrR_EMz02aNv|c#uLyPCe(!3rqD{69LL(MGzrk*jHW?iRVLR)Aloh19XAp>~oKikd5Eavife z3zUB$br4ijP_mXkF1WNwlA3_9(;^eia%U~Iu0@`$<>9xzR)F8Pw2*ohd9Rj=-#@iH z{03?T^(<;)DM<=Kxe{7ri(oCazC|rxT9R7mn&ix2J0i44bwf>ykqXwr>xG(5A=N}r zl}2O90~M}Q`Sr|HX+w)z3Uf*D>IZ8D4K4B%EyTkjU(`}PENV5( zIG&60a!;)QIZL#VMi%*7Efv2$S{{DKYXyxg>R$K+wHr4W*Vv-i zmDe6Nj*ug?5KoJ0#Vn10fJu`ZnN0^l1!-v>q4IB9fu}`osfBo1)P?qiGWE>r=b%DR zLhp2?tb_K@D?*LLRPq5+(IP#~>L^e&E>1eN2^5WsPRW;9k|J3z`VdqnT`e_yyq4!{Q4b=ZcQ2Hvh^f$wQ?P9ur~w8gf34;D zS>(!EfuBWPUAZs^29n?QgPf$1-hNu1zs2+pc-^%FzM<+vq(rSAX1T4F8bIBh7hqAZ z!Oul)o@Tj@77}PtmtZcCF5P0&AE0zCrxsL)X$(^BDJAD?1wj^d zHbMf8gREtEm`&$E`Du@WLe(;eDdca^Ue9dm2&#=1-Y8UEj8qr!6)oT2tlk4f{w-@V zT&j_l7i>{8Y7}Y&?~oP}Vv(J-)DVkl0^(bHEiEKeKB^Ufu2WNzT5FF$57$zgSmaw; zUK5Mj0l|saOcu=t)kizvA1dG0QbR4K(Av01*V3AVs=pxBkz0tK4RbF_%>>2mBDeSm zRBPR4P_nC55N1(R>+q?8k${zZwA65m>38tZ%D_-LTq^**8d0i?PXDT zb^vv6LW)L8?|b<#EyQ9`2i7m*AJCWwUWEomU!Z1FCs3V8pSl{Uw%j6%S-lC0dlp)( zHf;zicwV4cp00&N!ZPq$YvGZha+sC}dYT7XOXFes3MtrxoW@#eGmD(4<>B`|t)Lku zTw|?C^9a+V#(Ios9%?#)R9h{)d8q8K6*RY~BRnOkIjSc ztW@{XRrK%@1=Vb%sEF>t>i3{%!eRUyn`Jkxpru6}?agOWDe@@w7f`{Fzyw7gsHf$% zvY28q(VA#!^+VN_Na3Hgq()%^L8Z0Dl!`gqTT5#lYWfnXo_fkNK$4>M)MTW1?nR`! z>O7x7N$RVoG7D2Tk&4yQ+J)I6j>hPjTab#^Q_f~85Z)%t4v8T;>jF}7da5bR?XRb@ z3R91f;x+U}2;w&FM2gEf!PPh~0jVBTR{anu(KDFKkF|pK7PTE1d=?|-shOa{^u8mn zM9k=5QC|at{MJs($AGlbLONPZ8Q__S|q?bikHYKkm44& z!Y{bK{z!4&dZc)X`$+K;A(-9Vf+xJuRl_{K6NWn)Kw-ezph#SSqq7_ z$lh9NG(5N!pNMu^#ssWi(H7I|m^*K1kD^0O*@dYBq`K?8sMl!hRXTvDJ?a^X4Ml1%i=3t9;rDy30KXnuNNm)=wD3Og0;F2$DR;yo1TOG~BgJ#K7V;h-)n3nS-q|QI11UrT zl=uQEUcwo1oonfj6fcoe$h(geLJmqqb~SP{k>VD7ffO(6+Rf0{A1TgTj}+H;A1PiU zB-*fG3Q}C|1X5hi8Szlqf|PEbyjIH_Xfgc-y0>;Q0JP&%wMjB0D17aIGN0B5%?{ z60uq#CqUnTHXOormZ1HP8Y8GuTE4GYT?#4yHDHgMXjV^x!dyn=!%o8FG*rw1buv=K zhsC|k^1rl@B#WG?r6yU_PvhZ0dU?`Tc^Ic8l2gZkqEc9wu&(UWQb$@;`{8^~t?yj0 z!z>u7@4rV`2(+RDUfzCCqLFEhPuQnv7Jup86RnI1qU4 zlXQ!hGn4nM&w<@L5C^9|r2vy^dA}QSQv3RtDl^HLXXN)`g%o-E2vEFxiOL5>4dJt(M7l9wXulW=3b%Bgp|VvgNVllJfF2B4 zeX!JSR^B+i_nN2WSuN^U;L)5?Xc$bDGiZ3|&f*QEsAEg&jolB*Hl$@H7oCR@pePTG z#U09IEp@U*&9g~Fn@zVtVLMPiR1VcbreMD}g|`59pshPWQI~^4KimaH!%;%7*W)d| z+kMyySMp} z@ZM|&ss-|N%Jep=pf|kdp9*76t#}um_7m(0SfzE^eXyohQ+jKhHekSz!{{- z0*q^Ovuc{jWp&3iH3wy(`yF|amO9g-egc|mfh=~{k3msCp+`f_YSb+5()vg!t7d6? z{Ug-x!L;Zik>ky(`)o<_(u-r)V(JVEUE&w2W+FwwA6ulWe!pb+SXm`yuCMQP!%u-XFb z62ONuVskx83z=_G4}sSPyh{4CEv4~kiy@?#0Ez}5cS5Lf7AWc={ZWqkAt-VMOepli zBPvJFUR3Xe+{)5gK5p!0gCZ$j=3`Lg5^yHy^I61i(76*X07cp{tTW8&SD?sU5RDLV zoEF0lEqr5`9TLs7N1nKmLuxRkR9|L98W|>!1I7I{vZGnu2TBYo=FNRjG*eK;c(bYb z5>!E9OwQ0!7hBX1LGyVF$56{HHGG+dyg4Y+!{_D%P!u@PjTpEu1;v|Kaamz3z$)G! z6n`YO5vd^k#&epMx5T1;xt!Og2WFG^3flJ3{on+od~_RWzU6=t^L>ICdgeMPY7rcUmc%UH z`DoFyU^@_D`ct*dNMRH(#`lCk3A*W3y;t+D;_FBpC~h)Uvr^>oVEBc|<5Rb6HXk@n zg@bCWHxA|Sz$tZ=#q=q7cxW>=RCQg$P0}BhsXal7Ixtl8K~eC)J$#5+Jq0RAZx}pB zwOh;m56>b&MS!B+Kv_MQC4nLzLkFS@HiM!LEUVAnp9H0Mr|SMrp%6RZye_o3IXMwc0u(tmUIzCs4c`*)qM5f}@%agKDP72jyMb2GbLYL0alsi+Tcd zp?;I|di~yRbC?$Ljzw*@f$PUo1@{;WN^EWY&GH7V020>)5AzwDLg$UVk1@=?m;j)# z311tEC!PiCEb2a@VLM#1kJ;q#9*sM4P3@7w9%pT+%8=qUWVA4wj)B5$8YD{P&^A5% zolw&%q;T(v3Hk?8P4(2SO_DT1d$cgjZZpjW%Iu327u$u@Af4y3g?96lI|eD9dlD(G zq}*0AhjL#>ipPLiNcGZr*O3}T2B=fF@u7kEQ9`A@?!M=d`@d7L(gf{T3Qs zn5KnnK@0)iN_Pedis~*&8m13rvR1IgtZj2ItsVxC97NxPtA}hC0bKxe5tg-;Tb*4fH=0cpl1PB03b?Bc7SXE$*sk^RlVT5 zAV>k`rk;@lOp4*ydPQ&`9q$JwxkG?bfR6#B=o5jD15-zT0U)`v0J;d1+&KW1y9^*h zzC-=`^<5E61%CjLz)is?O!O}R>WMo7-vuW5`vAHK6a5DfxClD{N}>=MW>19$rUs^0 z1fwJpRe{OZ8UT}mMgn^Q(?yv0^sOGX#9#0UQ!)TQq%Z`SDtsNdG;n9&*MR#1mjfOK zOyv`R%c6d1oM5B^+arTM8l;ObDa-_R0e%;l3T_bm&A?C~?E$U~d& z+aJLACq1Aafk{sRXcN`=7zyfH1zbhzu)|p&6B8&ZQ4*MP%TSsB0aNwWgdAZ?)(|vd z(&q+D<^~FWF_`;AF&QZK!h-*$eBqNt4WN+^78bt#=t*C}Ip1co-452vctk7c^n=!z5tx zGx~~{>V6BDdV3`>UH<^{asCGd{%HmOgS_yw;tZ&SqVJ0q{jXue&;FANC}bWMhW=9= zS6l&##UDcVU}04CN+L7yT5S8=$cmbQn!Dwm|MgcM=Yau%4@qKm+k zby?u6MBpll$(rv4pD-nV5Oh&YdVdsr!W5u?RcMvviT^sL3h$#L3aKirqy{;VwNGKx zPdq35P=Sg9R}z9nG4;PI_|%B%BEKl6@-;+xH<3@66#VKS^-h!7xLLR-0)$DRnmEe=ywq{uId$-HLZ z6K*c@2~#yK1zi*qzcu)j)mG#Kr|ZG2qsZtiGG2s9QCA^Hn1W#sU^3(l!G95^^1Xyy zuM%4AlNGct_c_Rt*5M>y@%ss7{efxYF$kCn4Hkt6Q)NR0KVHzoL_T4XPZ0b>!G95^ z9nu);4N^Q-6dWfC5+;SIf-Z`QH4#5#Xa+F3+caR}&%_Vun?=NPFv-ss`SVL?*Y;P? zSEGf3MVP8y3{0w+;1`FU97(xbL@r@cw@uK5DSx}53De?uT<}i-m(U&^s8737=`$f!6w?a*jo=rDskRG3 z+qb}qcJVZKpUZ-KMc}KzbP=ZH_X7V2Ott?+%x5rZzbW`dF^$$;@G1XSkx!WP+%F+% zB@WR?nv(RJ;QcOS2$PFF68y)&)a?p>C{n7xB>EaLpTV>jcNP5NFx6d6R zFe$DFOoH`+DXS6v2uzLf1Wo?q2TXJTFm2i+fT^4XnDjIQri(D~n+x0mn98-X6CwU} zAxM~#9fUv>FjdqIm=r`)rofc!i65${4=~XK=;xo{^cNH$3kQmP!el@!Fb&Oc!GBo{ z75a0AXn!O~@vDao)Fs6}gn0F^fp^V!97%-9>3;x_8@_tj@aka$+yGb6d;kAkn%^2c zRUx_LiyktNL6lF??$yJF=RI(E^{@f%VSBb9{LIE6FX95P9yZX7d-bqEcL+SKcrK=Z z^Xg%PnY;y8ahN*q)x!pUPx9(v19=HOgn0F^;nl;2R}UNbLkGIg`v396hR62wx^zc_ z&ubpW14B-EizSn$yWN9wUcRBHH%&|GvcK2KRYA25-oBUl{_26pJ--+-;o_Ob)^~?a z3|;!coh$b*e82CT#`DL0ojIWC_{n8vbe#6*!|3?#%b;Dx%_zM>_9?12XJYv5RWmC7 z5%uP#tsj=SG_1~{^qr~QlZvBhyvE$d> z^l{sJ$hp^UIkNAq{FSe-Z83JrwfK-9H{ry{DZhIT zA8>QveC4C0tz8BkUfOJ3cbAVHKMpN7W_5!njrQ#9b+5s$rV}bJNIv(i+ubsmWiqBu zt$D@ek1DsS-|zj~p^%3~4E8Uow{g4C0}k}BcOw1n<$2pT?+nOT<{O;&)8NDICx7dE zz@f97)5hi(J6%8T*gf=Y@4sriYqxT8$CyQHlhbZ){3JfBO}O0&VK8}OKvBg__Ri~8 zwO2}wemTMG`&^1>zG3K!YL9n?wC&c&wZhegi~aV?b*AU~ImR^&I6vsxg1+fTpY%L+ z&dI)V?UYrKE@sdCuZ6{=*tmg2QnCxKAGz*8*X8wZb(EU=_q6w&xTIl|TFvKHe%C#1 zce}KYe{W)4*S7kc20hC5GyQO_$)>CmKAG_iwltXX-IDQN4jJ6RPZ(^4t{|Jic!l71 zI=Ry9??x`z=h@#H{Bix+7jN{K*zWGbd+BEvrv$dUGqb+eq(P(q7&GXj#)muhsPWCk z$A^9!F{b*1B*%680!nO|=Ba{Q78@7Ei# z(0y#>NYnSJUoLQZ?abI}(>kwPlbv|>=!gBUE)AR$of1)@L2BfQ{#Sh#g~;q`j{GJ* zMY5CAbB`>LhnJCuFyEDOtm&i=K67BB_Q{vo-5mKX`mvH6vu4SqO~>%jQh9cDtL)A8 zgOO4x`ht_{YEu%``pi*;Hlhp`no z$gYl$b{tsbR@s>y&XEtB?8XrNl)bvz_N1}g-8B7qt@R-=q;|{0RpURc7{kK1%2|pu znO)i{H&Kp-vJt!G5xLX1$!%o0W$u~n@^U-*2}|59k25XBXPbJZ6G*FS{Ox|-+)=sm zWx0qAi*x0nY{g!=v`gRy{d*EC4coBfq!EF@J7IenvV8*Uv|qN9|779&WNOWY^_ zX!>&_{w$nwlcAhnNfUaFajy7!rbq?eO&k^(*QAsX%q=r6> z>LQHJ6LL=Ac^Yr$_6os@V0sC`{enl20pAu14~oL{SqWaS>enH`a|T7Pfay9ccvX<%c z4i`MJyOcb@qwij*mFI+9Bc%PnBjdjjyv9g30M8xxJnmI=ul58%pA?Yw7X;4>X?m|r zmVPUE-bl9uj|{yic-Xi}^uC#RmjurjX?hn;#$OgZKcwmXH5q?J@X9n58`qD*S%jx@ zvo;A0hEN<}C}0?12p}FX2oP(>JiL^F>BnF!&Gke;9AFTj3!p2&0ZV zeffG1a0hS~a1HPy;0M5Wfb#(ACF;@B02&nwKDlQf#Q6kt{ZDERaM+=tqa_7jQk0=mY2n=nv=#=mi)6=nZ%S5Dn-Kpu4B~fZBjs z0J;;d1E58v0iYqEE}$Nu5x@)37~lz@yK%Z-q+2_8fCs==!31}AE4e%X+K4e-1NT-PH0ie%x=rf>C0Ve_UHRE-_Ex=Cz`afxZ z0o(@M0o(xG1^f)S2{;8f4LAe%9PkBT7~n^=_A{`D19|{xRiN*gwgNT-RsvQ5vH)`c^8j-JnSkDa5I_?^D1f3p1@}4t3f>f~ zYXImQqZ@$h^!L6$AmIg|Z@TEyr7M6_0AE;4x7ZtyrYBQj01D0&j4AkT1AjGOCV)N} z(*S({;eaqe5FilX380UgyaAU0W6?<3e$Z|s74=Ka&`mv&aTb_9kFO5M1b+@-9$-E| z11th82D}ZJ2*^g6@qkAF`U;9ZIL!xq0Qd-S1n?o?Fdz}~bh}0i?>Yc|??Y>AIxVKO zc+z4y9T~LlT?CAPFs);>ZuLT5Fu)9G`b?UZoW=lufDeF{8(LmyHF*y}D+sL^v_jB| zKq~;r5}=&BvQE&K3={g(1$s; zR$Syn)(FUz1{&KwYt>419W&g!ehN9?5EAsA!umrXkOmgJBx_a2oH^5PPDz45fEQGu z2a!Y8wf)zz1k;_h-Sixk^pke8%~Zul>}YGnUp~eDq?)cloSM0~W!%hB3+_iMs0oU& z_S|LOZBUZ}U&da}dSmFSp!G**R)&CRtuu1UAty7pb+6cI&ZTTQbyymTy3$l|gn-+U zKT3@2IcqWmpqcux8QTs4xf?q{aw8!}joEtpr_f(U^xCZ3;7@YX*%PvXR!#J+_08zP zrRMi{oUIG!-Ij|SY~-xrmA{)^rBVY2tYx--|8-_rH_lMo2@^7BOlH?z(Vz>o}Y zs|<1d7IL^zux8aex2Rr3{AaqN055EAuCv>uh@vU(8mw6tuC-@VMh}Gm#@yE{K>Cx_ zYNz-srV^}sJH<~f&t`yx-UIER_cP2D@}?OXTgJCp7Iq!Q$XT!(_?B7PD=tA4)W`|m zxs!W+!h%tIbiDy2K-&V!IW9Y%kIMP!p>C2t`I*U%&1tWAxYG^<`!%Zxl4SgZDxnYK zr*#|bAR9Sj*#%T0r?b8t6c<*$gHlzbU5#@Ae%dh8{_NS3BNh2EwBh!|%7tBx|Fz%C zJTI`_#%_a9y+166XIx?Pp-ujUA+Sl4YE^2OWA56wgi(Q_5pWfCxs4VW{^ zEoO4LC1ZplY(l-6Q%9wP9Km8bD$C_x*`tn1OZ7r2-qF`tyD0RJaX5kJpzRb<+7(^ZSf zozR3+%oWJBCPs)_{=oXUO{Y%>{0s|7>6pn^@BsrMWq}Soaa?5Qph79C^xgO#c* zruR#9`HOq%Y1b)Q&t^8^WvUx*Sou^to-x9nrT0No>anGLFu_CFNl>o*kPfV#Z%2Ky zeM$Q_5Oc^?5ne^quVUftC1OMUgyM_KggLAIPirwCPs8xbt^9Dhg69wVjjioMQ>0k0 z`lr2qkIhO|T%4YLPT?T`#Bx)WTE>lK(wo>aMzBV7W7(F?eG_On>%SFG2b#XAR8Wn* zpSTUxrFXN;_0LMX8V8u2`KIa|)4^>11WMm^c(99aVlydjUU^lnI1o*LU=`wBeS2L)at^CBQSo;&PNJIc-7B-+uJDkG@-EhUq7o{9 z#TfGCtL(%?Wfw2$0%WnmVn>!S6T9T&X-bVi<1Dg$6)RWhJ9p`1I~fmI=zd-@PAl8> zLMU;UO&3RrRciLI=0%GwW+bxZ%=yv0YhoG5misx=OmGS}Z6?Dywqt!x4s ztCf!WcCt_)SL0Z>ZkG;aPx~#Ss!i@Lt2JA33greFr@>W!GfKWb@hhuMtR8MU@HW>v zxn0)qpEs6iZ_8=Q4p2pX*w^Xk;{euqol=pxS#=NEx3Avv+3M^9WL=H3 z*UCA!xUp)>*rPVtR?I#F0>)u&@p)kz0!k+SU=ujSu8MjSSYHw~4t&dL72kKXrTz+= z=pr^B0`ex7Lvn}1`Gz`Sz?vs#=jTP**IsYKI`7bQ{sbosYOqRudqjx@%-fmNYd@ke5asB3u_1DY8)1~ zF=WxmE-t@j+0mmCe|lEZm!(4>&^TT0o14>_`+^N8Z34#mbE`IAN_aRRdYvuj9d?p* z8wb`!et!AG6}{h0wF#VO@>{UsE^`HPRq@u1JTI|h&});EJ50C9xw9w|2!{YYK0VQW z@yXA>oZP>n91-s073k$_7L6IlR=x$-v}b2X?Q!-qp>fPKRq+orPO&>M`?ppV@1%Zg zd;DjdXQw8GeSEX?GTeJi3iR>|!c*5AmN*sl8V8vrG`z1is`nRDFy+xb=$ySQd#ch% z{*;}Zsw5flCMUPY+e!&J@b6KoJYHtdTL!^ z_aVm~0~Nn^P5C_)Dzvoa9A|y!pr-Q>pb(w;~s%gLf(Cnde-frEJ1n)HBocwM{O7-60!{b1376 zesgoi_m{r32|W8M*Z~U{7FYDlBNnbH9h7pT*=!BXb!LY&)L4^!1FoxaOyT%y@t<9t z-tSk@pBUc&7P$~|5v(_mYx~i>cV>vuLY?#2{N>n=M=rv4 zTp#|*CS;nD*}_Ho`_7|_V7Xf|_u!kIV<&W))nF2|U@$1y{lL5zqZ;Gz?k+RVZmID` z+cKgWjMqIDyBOP)CMkUSaCx~U+m2##G&@14AG^L7!C?s7w+Xt812(5R^hro~OI|^3 zqL;D0(oxodLDy$v_{L%f%V2oxR(c#;!SMWMAq(FOW!daBxPitgnvW9XZ6j;9Am^oy zLEAdvOAiNl(7zr|82Z5*3)d-f2sqB*3}Drlz<_j+^lo-g&ksLXWxv{_$iu*?1WqRF z41vJ+K++b>x6HktJz7Ok4 zwRRo^@K}`QlyUrLFL}j_o^P(Xpvje1$cZYnC-6JZ5m0gCy z#v!4dCME3ucKeP^u)@r(=*29{Fp5`M@-hUeoh%>XaxS|-rHms%r)-Nm?GX}1%@$un zoM!gR@lHX_U@ev_ey&?Gc;Bz_Kie^DU4dB$V%T&??NH2p3bS9KRF@ku&lSoLo~_@y z=wZ)AUd)cIfK?vs(F&Lo!JJnr8Q9sKSp}<%<2=LfAKrf4A&_4Co6%G7+Oh29N(dO| zdwO`c`RiZ3E7FV=jm3kVRf@l>aU|!gK?}F_2H30|4) zSOrV<%KhcZ%#?+x^>+=V0LzNhoF!&K!(29v0shT8*MnY!)FNR(QP?Yw8OR-216dZESb!j5*Ttg4eR~ykPAdzP~N{ zyr6(ZW@FOX&1HSE5g>}(HnI)bsJa`whp+3V zIiy1a`@iem`1T^$fLjZ)!8oaO%eVV()kS1bD}~$URtzb?~#lf6!s5*9X8w-pGcpE50GLFu|i^ zSr!B^i}Rolq2e|u1%H85`dt{ckA=RA(#GkgxBfNoVt=R70k%eTXGtV*c^P+`odq?{ z&$F(g?^(?JM&~BW*iIh?psI)L0A$4@sZ#oFP^snYv+elAg8bEqmBj-mSL5{1mEG^$ zzxUITqtALEyp|qgxf-X97EE8f|F;`?h#bbVCH={jzx+DOhdu=>*0<}i3Upy7w&8W6 z{RYG!KI`aEGd=~_ybbt9#jxI-7 zmCu6D>MhpC)q#0#L{C;{F+fK3Y|BQaBfb)Pv=OF1Kh{@azVD%n+*vyySL1BgoLSj# zPcHX>KE|Qo2;T{3>F=Qq9l6L^>?BdWn4E(zYU0_-98d?@nH;Fv!hQyFby&j(sMZDV ze)ap+`jOxJlBy%jZ4<-`7v4lHwODwk5%c*AFU2AhKFaQp9A9wxLc9EV5lOEL5sBgs zB9cGijvkD7V3GOrMmA@Q67U>%!b;>w{C2P{r_2U6X&Y*BVM#mS zr@S?lOZZ8;r?!+EFvsmmG{py-F)s2$wtPFJMPX5@y-^x>?a!}JXcDqj49s_Tw7JiF z9}fGp<|^=kS}+e^0)-M1UC3>&_UyEGpl0iiA;yN)+=*unx^yKrZ?|H{&hAhGM9mfT zT-V|a4#1%D*in*_!gg12daw0(<%gBZ*x&n#cPB}7W|=$T=*Drs4_4S;eS6RGOq;-5 zmJfla>K3lRJ;Z3-QxeZ7su|}8H;amn>X4}&wAEC!@ztK+Jl-b1r>YQxQSI_j{sCs{ zy^j5B|8C)8myl?5h}_<|p-LTX&;tjK3I7T5{Tc()9Lt zyoI&GkG?HD%D&r;t%PwPvHybDH}hAnPE+Kd?L68=F?kOj0{72VOs>W`#<^9FeEZpF zkH3c+iUz*H(nyw`i+Qcb(aOPCTb`}aK|T+K^cmmZtE%&Uk*ZweSmwG1HXDa5ce<0k zzSk!c`=Bxkf%x3?saw+Yh{pUjqUMt-cDEM&@s@*3J0-e|Q;&_KitP$ND>lwWHV!kk zBoSAGZWE{|JoT_ZgnYNqXv>N9w+j5LUo{f{4 zZ8`e4;;8R1eKU@$OjvRp+qOrEV0ZT@KJr>tD^GE&rT;B4I+{K9Z_FO?R>w zh&5S1ny(8Nq9?r5Bd4az`=II3Yld@l`6m?vE*%8z0QyKs|E`v?O}@S>D)mldd-Ig~ z<@7%<9+vE_EG%En!#2gZ-kY1TQUM78v$Plx_D4-Z_%;>~t_=93bqrl(91)Iyq8P${eg!mC-#{W&8bN{ZsP}(zbOmf0g78NO? zXR_@-D%JhyPp#1xwwiREx&Yc;9Gi_CSfZ{^F{;r8e=lb+^->8bf4|-ACi*R)Lr+cW-NrW+lw9TpLzc0}6 IhpeXm13b~T(f|Me delta 32976 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 CZgx-r diff --git a/src/site/package.json b/src/site/package.json index 64c7d498..cc8fbaf2 100644 --- a/src/site/package.json +++ b/src/site/package.json @@ -8,7 +8,11 @@ "build": "lingui extract --overwrite && lingui compile && vite build", "preview": "vite preview", "sync": "lingui extract --overwrite && lingui compile", - "sync_and_purge": "lingui extract --overwrite --clean && lingui compile" + "sync_and_purge": "lingui extract --overwrite --clean && lingui compile", + "format": "biome format --write .", + "lint": "biome lint .", + "check": "biome check .", + "check:fix": "biome check --fix ." }, "dependencies": { "@henrygd/queue": "^1.0.7", @@ -49,6 +53,7 @@ "valibot": "^0.42.1" }, "devDependencies": { + "@biomejs/biome": "2.2.3", "@lingui/cli": "^5.4.1", "@lingui/swc-plugin": "^5.6.1", "@lingui/vite-plugin": "^5.4.1", diff --git a/src/site/src/components/routes/home.tsx b/src/site/src/components/routes/home.tsx index 09312e8d..46327f75 100644 --- a/src/site/src/components/routes/home.tsx +++ b/src/site/src/components/routes/home.tsx @@ -1,22 +1,22 @@ -import { Suspense, memo, useEffect, useMemo } from "react" -import { Card, CardContent, CardHeader, CardTitle } from "../ui/card" -import { $alerts, $allSystemsById } from "@/lib/stores" -import { useStore } from "@nanostores/react" -import { GithubIcon } from "lucide-react" -import { Separator } from "../ui/separator" -import { AlertRecord } from "@/types" -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" -import { $router, Link } from "../router" import { Plural, Trans, useLingui } from "@lingui/react/macro" +import { useStore } from "@nanostores/react" import { getPagePath } from "@nanostores/router" -import { alertInfo } from "@/lib/alerts" +import { GithubIcon } from "lucide-react" +import { memo, Suspense, useEffect, useMemo } from "react" +import { $router, Link } from "@/components/router" import SystemsTable from "@/components/systems-table/systems-table" +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Separator } from "@/components/ui/separator" +import { alertInfo } from "@/lib/alerts" +import { $alerts, $allSystemsById } from "@/lib/stores" +import type { AlertRecord } from "@/types" -export default memo(function () { +export default memo(() => { const { t } = useLingui() useEffect(() => { - document.title = t`Dashboard` + " / Beszel" + document.title = `${t`Dashboard`} / Beszel` }, [t]) return useMemo( @@ -32,6 +32,7 @@ export default memo(function () { href="https://github.com/henrygd/beszel" target="_blank" className="flex items-center gap-0.5 text-muted-foreground hover:text-foreground duration-75" + rel="noopener" > GitHub @@ -40,6 +41,7 @@ export default memo(function () { href="https://github.com/henrygd/beszel/releases" target="_blank" className="text-muted-foreground hover:text-foreground duration-75" + rel="noopener" > Beszel {globalThis.BESZEL.HUB_VERSION} @@ -71,6 +73,7 @@ const ActiveAlerts = () => { return { activeAlerts, alertsKey } }, [alerts]) + // biome-ignore lint/correctness/useExhaustiveDependencies: alertsKey is inclusive return useMemo(() => { if (activeAlerts.length === 0) { return null diff --git a/src/site/src/components/routes/system.tsx b/src/site/src/components/routes/system.tsx index 22a76ff0..3fcb32a4 100644 --- a/src/site/src/components/routes/system.tsx +++ b/src/site/src/components/routes/system.tsx @@ -1,24 +1,34 @@ import { t } from "@lingui/core/macro" -import { Plural, Trans } from "@lingui/react/macro" +import { Plural, Trans, useLingui } from "@lingui/react/macro" +import { useStore } from "@nanostores/react" +import { getPagePath } from "@nanostores/router" +import { timeTicks } from "d3-time" +import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from "lucide-react" +import { subscribeKeys } from "nanostores" +import React, { type JSX, memo, useCallback, useEffect, useMemo, useRef, useState } from "react" +import AreaChartDefault from "@/components/charts/area-chart" +import ContainerChart from "@/components/charts/container-chart" +import DiskChart from "@/components/charts/disk-chart" +import GpuPowerChart from "@/components/charts/gpu-power-chart" +import { useContainerChartConfigs } from "@/components/charts/hooks" +import LoadAverageChart from "@/components/charts/load-average-chart" +import MemChart from "@/components/charts/mem-chart" +import SwapChart from "@/components/charts/swap-chart" +import TemperatureChart from "@/components/charts/temperature-chart" +import { getPbTimestamp, pb } from "@/lib/api" +import { ChartType, Os, SystemStatus, Unit } from "@/lib/enums" +import { batteryStateTranslations } from "@/lib/i18n" import { - $systems, + $allSystemsByName, $chartTime, $containerFilter, - $userSettings, $direction, $maxValues, + $systems, $temperatureFilter, - $allSystemsByName, + $userSettings, } from "@/lib/stores" -import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types" -import { useContainerChartConfigs } from "@/components/charts/hooks" -import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums" -import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react" -import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card" -import { useStore } from "@nanostores/react" -import Spinner from "../spinner" -import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from "lucide-react" -import ChartTimeSelect from "../charts/chart-time-select" +import { useIntersectionObserver } from "@/lib/use-intersection-observer" import { chartTimeData, cn, @@ -30,34 +40,33 @@ import { toFixedFloat, useBrowserStorage, } from "@/lib/utils" -import { getPbTimestamp, pb } from "@/lib/api" +import type { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types" +import ChartTimeSelect from "../charts/chart-time-select" +import { $router, navigate } from "../router" +import Spinner from "../spinner" +import { Button } from "../ui/button" +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card" +import { AppleIcon, ChartAverage, ChartMax, FreeBsdIcon, Rows, TuxIcon, WindowsIcon } from "../ui/icons" +import { Input } from "../ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" import { Separator } from "../ui/separator" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip" -import { Button } from "../ui/button" -import { Input } from "../ui/input" -import { ChartAverage, ChartMax, Rows, TuxIcon, WindowsIcon, AppleIcon, FreeBsdIcon } from "../ui/icons" -import { useIntersectionObserver } from "@/lib/use-intersection-observer" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" -import { timeTicks } from "d3-time" -import { useLingui } from "@lingui/react/macro" -import { $router, navigate } from "../router" -import { getPagePath } from "@nanostores/router" -import { batteryStateTranslations } from "@/lib/i18n" -import AreaChartDefault from "@/components/charts/area-chart" -import ContainerChart from "@/components/charts/container-chart" -import MemChart from "@/components/charts/mem-chart" -import DiskChart from "@/components/charts/disk-chart" -import SwapChart from "@/components/charts/swap-chart" -import TemperatureChart from "@/components/charts/temperature-chart" -import GpuPowerChart from "@/components/charts/gpu-power-chart" -import LoadAverageChart from "@/components/charts/load-average-chart" -import { subscribeKeys } from "nanostores" -const cache = new Map() +type ChartTimeData = { + time: number + data: { + ticks: number[] + domain: number[] + } + chartTime: ChartTimes +} + + +const cache = new Map() // create ticks and domain for charts function getTimeData(chartTime: ChartTimes, lastCreated: number) { - const cached = cache.get("td") + const cached = cache.get("td") as ChartTimeData | undefined if (cached && cached.chartTime === chartTime) { if (!lastCreated || cached.time >= lastCreated) { return cached.data @@ -79,7 +88,7 @@ function getTimeData(chartTime: ChartTimes, lastCreated: number) { function addEmptyValues( prevRecords: T[], newRecords: T[], - expectedInterval: number + expectedInterval: number, ) { const modifiedRecords: T[] = [] let prevTime = (prevRecords.at(-1)?.created ?? 0) as number @@ -90,7 +99,7 @@ function addEmptyValues( const interval = record.created - prevTime // if interval is too large, add a null record if (interval > expectedInterval / 2 + expectedInterval) { - // @ts-ignore + // @ts-expect-error modifiedRecords.push({ created: null, stats: null }) } } @@ -100,8 +109,9 @@ function addEmptyValues( return modifiedRecords } -async function getStats(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise { - const lastCached = cache.get(`${system.id}_${chartTime}_${collection}`)?.at(-1)?.created as number +async function getStats(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise { + const cachedStats = cache.get(`${system.id}_${chartTime}_${collection}`) as T[] | undefined + const lastCached = cachedStats?.at(-1)?.created as number return await pb.collection(collection).getFullList({ filter: pb.filter("system={:id} && created > {:created} && type={:type}", { id: system.id, @@ -137,6 +147,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { const [chartLoading, setChartLoading] = useState(true) const isLongerChart = chartTime !== "1h" const userSettings = $userSettings.get() + const chartWrapRef = useRef(null) useEffect(() => { document.title = `${name} / Beszel` @@ -160,10 +171,11 @@ export default memo(function SystemDetail({ name }: { name: string }) { }) }, [name]) + // biome-ignore lint/correctness/useExhaustiveDependencies: not necessary const chartData: ChartData = useMemo(() => { const lastCreated = Math.max( (systemStats.at(-1)?.created as number) ?? 0, - (containerData.at(-1)?.created as number) ?? 0 + (containerData.at(-1)?.created as number) ?? 0, ) return { systemStats, @@ -178,8 +190,29 @@ export default memo(function SystemDetail({ name }: { name: string }) { // Share chart config computation for all container charts const containerChartConfigs = useContainerChartConfigs(containerData) + // make container stats for charts + const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => { + const containerData = [] as ChartData["containerData"] + for (let { created, stats } of containers) { + if (!created) { + // @ts-expect-error add null value for gaps + containerData.push({ created: null }) + continue + } + created = new Date(created).getTime() + // @ts-expect-error not dealing with this rn + const containerStats: ChartData["containerData"][0] = { created } + for (const container of stats) { + containerStats[container.n] = container + } + containerData.push(containerStats) + } + setContainerData(containerData) + }, []) + // get stats - useEffect(() => { + // biome-ignore lint/correctness/useExhaustiveDependencies: not necessary + useEffect(() => { if (!system.id || !chartTime) { return } @@ -223,25 +256,6 @@ export default memo(function SystemDetail({ name }: { name: string }) { }) }, [system, chartTime]) - // make container stats for charts - const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => { - const containerData = [] as ChartData["containerData"] - for (let { created, stats } of containers) { - if (!created) { - // @ts-ignore add null value for gaps - containerData.push({ created: null }) - continue - } - created = new Date(created).getTime() - // @ts-ignore not dealing with this rn - let containerStats: ChartData["containerData"][0] = { created } - for (let container of stats) { - containerStats[container.n] = container - } - containerData.push(containerStats) - } - setContainerData(containerData) - }, []) // values for system info bar const systemInfo = useMemo(() => { @@ -303,10 +317,10 @@ export default memo(function SystemDetail({ name }: { name: string }) { ] as { value: string | number | undefined label?: string - Icon: any + Icon: React.ElementType hide?: boolean }[] - }, [system.info, t]) + }, [system, t]) /** Space for tooltip if more than 12 containers */ useEffect(() => { @@ -315,12 +329,12 @@ export default memo(function SystemDetail({ name }: { name: string }) { return } const tooltipHeight = (Object.keys(containerData[0]).length - 11) * 17.8 - 40 - const wrapperEl = document.getElementById("chartwrap") as HTMLDivElement + const wrapperEl = chartWrapRef.current as HTMLDivElement const wrapperRect = wrapperEl.getBoundingClientRect() const chartRect = netCardRef.current.getBoundingClientRect() const distanceToBottom = wrapperRect.bottom - chartRect.bottom setBottomSpacing(tooltipHeight - distanceToBottom) - }, [netCardRef, containerData]) + }, [containerData]) // keyboard navigation between systems useEffect(() => { @@ -343,15 +357,17 @@ export default memo(function SystemDetail({ name }: { name: string }) { } switch (e.key) { case "ArrowLeft": - case "h": + case "h": { const prevIndex = (currentIndex - 1 + systems.length) % systems.length persistChartTime.current = true return navigate(getPagePath($router, "system", { name: systems[prevIndex].name })) + } case "ArrowRight": - case "l": + case "l": { const nextIndex = (currentIndex + 1) % systems.length persistChartTime.current = true return navigate(getPagePath($router, "system", { name: systems[nextIndex].name })) + } } } return listen(document, "keyup", handleKeyUp) @@ -380,7 +396,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { return ( <> -
+
{/* system info */}
@@ -406,7 +422,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { {translatedStatus}
- {systemInfo.map(({ value, label, Icon, hide }, i) => { + {systemInfo.map(({ value, label, Icon, hide }) => { if (hide || !value) { return null } @@ -416,7 +432,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
) return ( -
+
{label ? ( @@ -479,8 +495,8 @@ export default memo(function SystemDetail({ name }: { name: string }) { opacity: 0.4, }, ]} - tickFormatter={(val) => toFixedFloat(val, 2) + "%"} - contentFormatter={({ value }) => decimalString(value) + "%"} + tickFormatter={(val) => `${toFixedFloat(val, 2)}%`} + contentFormatter={({ value }) => `${decimalString(value)}%`} /> @@ -558,11 +574,11 @@ export default memo(function SystemDetail({ name }: { name: string }) { ]} tickFormatter={(val) => { const { value, unit } = formatBytes(val, true, userSettings.unitDisk, true) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={({ value }) => { const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, true) - return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit + return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` }} /> @@ -603,12 +619,12 @@ export default memo(function SystemDetail({ name }: { name: string }) { }, ]} tickFormatter={(val) => { - let { value, unit } = formatBytes(val, true, userSettings.unitNet, false) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + const { value, unit } = formatBytes(val, true, userSettings.unitNet, false) + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={(data) => { const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false) - return decimalString(value, value >= 100 ? 1 : 2) + " " + unit + return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}` }} /> @@ -682,7 +698,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { description={`${t({ message: "Current state", comment: "Context: Battery state", - })}: ${batteryStateTranslations[systemStats.at(-1)?.stats.bat![1] ?? 0]()}`} + })}: ${batteryStateTranslations[systemStats.at(-1)?.stats.bat?.[1] ?? 0]()}`} > toFixedFloat(val, 2) + "%"} - contentFormatter={({ value }) => decimalString(value) + "%"} + tickFormatter={(val) => `${toFixedFloat(val, 2)}%`} + contentFormatter={({ value }) => `${decimalString(value)}%`} /> { const { value, unit } = formatBytes(val, false, Unit.Bytes, true) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={({ value }) => { const { value: convertedValue, unit } = formatBytes(value, false, Unit.Bytes, true) - return decimalString(convertedValue) + " " + unit + return `${decimalString(convertedValue)} ${unit}` }} /> @@ -819,11 +835,11 @@ export default memo(function SystemDetail({ name }: { name: string }) { maxToggled={maxValues} tickFormatter={(val) => { const { value, unit } = formatBytes(val, true, userSettings.unitDisk, true) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={({ value }) => { const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, true) - return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit + return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` }} /> @@ -846,7 +862,7 @@ function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilt const handleChange = useCallback((e: React.ChangeEvent) => { store.set(e.target.value) - }, []) + }, [store]) return ( <> diff --git a/src/site/src/components/ui/alert-dialog.tsx b/src/site/src/components/ui/alert-dialog.tsx index 443fedd5..ab18153b 100644 --- a/src/site/src/components/ui/alert-dialog.tsx +++ b/src/site/src/components/ui/alert-dialog.tsx @@ -1,8 +1,7 @@ -import * as React from "react" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" - -import { cn } from "@/lib/utils" +import * as React from "react" import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" const AlertDialog = AlertDialogPrimitive.Root diff --git a/src/site/src/components/ui/badge.tsx b/src/site/src/components/ui/badge.tsx index 2d34fa02..6c654f77 100644 --- a/src/site/src/components/ui/badge.tsx +++ b/src/site/src/components/ui/badge.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/button.tsx b/src/site/src/components/ui/button.tsx index 3d7041fa..4d7710ad 100644 --- a/src/site/src/components/ui/button.tsx +++ b/src/site/src/components/ui/button.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/chart.tsx b/src/site/src/components/ui/chart.tsx index d08c8ce5..10cd64ed 100644 --- a/src/site/src/components/ui/chart.tsx +++ b/src/site/src/components/ui/chart.tsx @@ -1,10 +1,8 @@ +import type { JSX } from "react" import * as React from "react" import * as RechartsPrimitive from "recharts" - import { chartTimeData, cn } from "@/lib/utils" -import { ChartData } from "@/types" - -import type { JSX } from "react" +import type { ChartData } from "@/types" // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const @@ -134,7 +132,7 @@ const ChartTooltipContent = React.forwardRef< payload = payload?.filter((item) => (item.name as string)?.toLowerCase().includes(filter.toLowerCase())) } if (itemSorter) { - // @ts-ignore + // @ts-expect-error payload?.sort(itemSorter) } }, [itemSorter, payload]) @@ -331,7 +329,7 @@ function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: } let cachedAxis: JSX.Element -const xAxis = function ({ domain, ticks, chartTime }: ChartData) { +const xAxis = ({ domain, ticks, chartTime }: ChartData) => { if (cachedAxis && domain[0] === cachedAxis.props.domain[0]) { return cachedAxis } diff --git a/src/site/src/components/ui/checkbox.tsx b/src/site/src/components/ui/checkbox.tsx index 1f2edb7b..772f0eda 100644 --- a/src/site/src/components/ui/checkbox.tsx +++ b/src/site/src/components/ui/checkbox.tsx @@ -1,8 +1,8 @@ "use client" -import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/collapsible.tsx b/src/site/src/components/ui/collapsible.tsx index f30abcb0..ca7cd89b 100644 --- a/src/site/src/components/ui/collapsible.tsx +++ b/src/site/src/components/ui/collapsible.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import { ChevronDownIcon, HourglassIcon } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" import { Button } from "./button" @@ -17,11 +17,7 @@ export function Collapsible({ title, children, description, defaultOpen = false, return (
- - {description && ( -
- {description} -
- )} + {description &&
{description}
} {isOpen && (
-
- {children} -
+
{children}
)}
) -} \ No newline at end of file +} diff --git a/src/site/src/components/ui/command.tsx b/src/site/src/components/ui/command.tsx index 31f8f922..db170f30 100644 --- a/src/site/src/components/ui/command.tsx +++ b/src/site/src/components/ui/command.tsx @@ -1,9 +1,8 @@ -import * as React from "react" import { Command as CommandPrimitive } from "cmdk" import { SearchIcon } from "lucide-react" - -import { cn } from "@/lib/utils" +import type * as React from "react" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { cn } from "@/lib/utils" function Command({ className, ...props }: React.ComponentProps) { return ( diff --git a/src/site/src/components/ui/dialog.tsx b/src/site/src/components/ui/dialog.tsx index 793f6431..6510f5f2 100644 --- a/src/site/src/components/ui/dialog.tsx +++ b/src/site/src/components/ui/dialog.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/dropdown-menu.tsx b/src/site/src/components/ui/dropdown-menu.tsx index d3042c0c..52a436e0 100644 --- a/src/site/src/components/ui/dropdown-menu.tsx +++ b/src/site/src/components/ui/dropdown-menu.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/icons.tsx b/src/site/src/components/ui/icons.tsx index 3c493cbb..96d2cc1b 100644 --- a/src/site/src/components/ui/icons.tsx +++ b/src/site/src/components/ui/icons.tsx @@ -1,4 +1,4 @@ -import { SVGProps } from "react" +import type { SVGProps } from "react" // linux-logo-bold from https://github.com/phosphor-icons/core (MIT license) export function TuxIcon(props: SVGProps) { diff --git a/src/site/src/components/ui/input-copy.tsx b/src/site/src/components/ui/input-copy.tsx index 47f00e75..319faf0a 100644 --- a/src/site/src/components/ui/input-copy.tsx +++ b/src/site/src/components/ui/input-copy.tsx @@ -1,9 +1,9 @@ -import { copyToClipboard } from "@/lib/utils" -import { Input } from "./input" import { Trans } from "@lingui/react/macro" -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip" import { CopyIcon } from "lucide-react" +import { copyToClipboard } from "@/lib/utils" import { Button } from "./button" +import { Input } from "./input" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip" export function InputCopy({ value, id, name }: { value: string; id: string; name: string }) { return ( diff --git a/src/site/src/components/ui/input-tags.tsx b/src/site/src/components/ui/input-tags.tsx index dc4340a1..cf9c4099 100644 --- a/src/site/src/components/ui/input-tags.tsx +++ b/src/site/src/components/ui/input-tags.tsx @@ -1,9 +1,9 @@ +import { XIcon } from "lucide-react" import * as React from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { XIcon } from "lucide-react" -import { type InputProps } from "./input" import { cn } from "@/lib/utils" +import type { InputProps } from "./input" type InputTagsProps = Omit & { value: string[] diff --git a/src/site/src/components/ui/input.tsx b/src/site/src/components/ui/input.tsx index 56398504..bcd2f073 100644 --- a/src/site/src/components/ui/input.tsx +++ b/src/site/src/components/ui/input.tsx @@ -1,4 +1,4 @@ -import * as React from "react" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/label.tsx b/src/site/src/components/ui/label.tsx index f966e86d..b01b5c86 100644 --- a/src/site/src/components/ui/label.tsx +++ b/src/site/src/components/ui/label.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/otp.tsx b/src/site/src/components/ui/otp.tsx index 45118863..ab561470 100644 --- a/src/site/src/components/ui/otp.tsx +++ b/src/site/src/components/ui/otp.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import { OTPInput, OTPInputContext } from "input-otp" import { MinusIcon } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/select.tsx b/src/site/src/components/ui/select.tsx index c72a27c6..e7b1483b 100644 --- a/src/site/src/components/ui/select.tsx +++ b/src/site/src/components/ui/select.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as SelectPrimitive from "@radix-ui/react-select" import { Check, ChevronDown, ChevronUp } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" @@ -78,8 +78,7 @@ const SelectContent = React.forwardRef< {children} diff --git a/src/site/src/components/ui/separator.tsx b/src/site/src/components/ui/separator.tsx index e309634e..0fbea8e7 100644 --- a/src/site/src/components/ui/separator.tsx +++ b/src/site/src/components/ui/separator.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/sheet.tsx b/src/site/src/components/ui/sheet.tsx index 187fd959..b14a88cb 100644 --- a/src/site/src/components/ui/sheet.tsx +++ b/src/site/src/components/ui/sheet.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as SheetPrimitive from "@radix-ui/react-dialog" import { XIcon } from "lucide-react" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/slider.tsx b/src/site/src/components/ui/slider.tsx index fabb9368..bc34e4e3 100644 --- a/src/site/src/components/ui/slider.tsx +++ b/src/site/src/components/ui/slider.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as SliderPrimitive from "@radix-ui/react-slider" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/switch.tsx b/src/site/src/components/ui/switch.tsx index dcb54f77..1ab30f22 100644 --- a/src/site/src/components/ui/switch.tsx +++ b/src/site/src/components/ui/switch.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as SwitchPrimitives from "@radix-ui/react-switch" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/tabs.tsx b/src/site/src/components/ui/tabs.tsx index 842f3029..5b48294d 100644 --- a/src/site/src/components/ui/tabs.tsx +++ b/src/site/src/components/ui/tabs.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as TabsPrimitive from "@radix-ui/react-tabs" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/toast.tsx b/src/site/src/components/ui/toast.tsx index df5c33e6..d1d60901 100644 --- a/src/site/src/components/ui/toast.tsx +++ b/src/site/src/components/ui/toast.tsx @@ -1,7 +1,7 @@ -import * as React from "react" import * as ToastPrimitives from "@radix-ui/react-toast" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/toaster.tsx b/src/site/src/components/ui/toaster.tsx index 43255ab2..0ad35d12 100644 --- a/src/site/src/components/ui/toaster.tsx +++ b/src/site/src/components/ui/toaster.tsx @@ -6,18 +6,16 @@ export function Toaster() { return ( - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - -
- {title && {title}} - {description && {description}} -
- {action} - -
- ) - })} + {toasts.map(({ id, title, description, action, ...props }) => ( + +
+ {title && {title}} + {description && {description}} +
+ {action} + +
+ ))}
) diff --git a/src/site/src/components/ui/tooltip.tsx b/src/site/src/components/ui/tooltip.tsx index 3d907709..d273e8f2 100644 --- a/src/site/src/components/ui/tooltip.tsx +++ b/src/site/src/components/ui/tooltip.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/use-toast.ts b/src/site/src/components/ui/use-toast.ts index b76ec1ab..403efff6 100644 --- a/src/site/src/components/ui/use-toast.ts +++ b/src/site/src/components/ui/use-toast.ts @@ -103,7 +103,7 @@ export const reducer = (state: State, action: Action): State => { ? { ...t, open: false, - } + } : t ), } diff --git a/src/site/src/lib/alerts.ts b/src/site/src/lib/alerts.ts index f94cd0eb..c5f93be5 100644 --- a/src/site/src/lib/alerts.ts +++ b/src/site/src/lib/alerts.ts @@ -1,9 +1,9 @@ -import type { AlertInfo, AlertRecord } from "@/types" -import type { RecordSubscription } from "pocketbase" -import { $alerts } from "@/lib/stores" -import { EthernetIcon } from "@/components/ui/icons" -import { ServerIcon, CpuIcon, MemoryStickIcon, HardDriveIcon, ThermometerIcon, HourglassIcon } from "lucide-react" import { t } from "@lingui/core/macro" +import { CpuIcon, HardDriveIcon, HourglassIcon, MemoryStickIcon, ServerIcon, ThermometerIcon } from "lucide-react" +import type { RecordSubscription } from "pocketbase" +import { EthernetIcon } from "@/components/ui/icons" +import { $alerts } from "@/lib/stores" +import type { AlertInfo, AlertRecord } from "@/types" import { pb } from "./api" /** Alert info for each alert type */ @@ -14,7 +14,7 @@ export const alertInfo: Record = { icon: ServerIcon, desc: () => t`Triggers when status switches between up and down`, /** "for x minutes" is appended to desc when only one value */ - singleDesc: () => t`System` + " " + t`Down`, + singleDesc: () => `${t`System`} ${t`Down`}`, }, CPU: { name: () => t`CPU Usage`, @@ -127,7 +127,7 @@ export const alertManager = (() => { return (data: RecordSubscription) => { const { record } = data batch.set(`${record.system}${record.name}`, data) - clearTimeout(timeout!) + clearTimeout(timeout) timeout = setTimeout(() => { const groups = { create: [], update: [], delete: [] } as Record for (const { action, record } of batch.values()) { diff --git a/src/site/src/lib/api.ts b/src/site/src/lib/api.ts index a81e9232..38a52145 100644 --- a/src/site/src/lib/api.ts +++ b/src/site/src/lib/api.ts @@ -1,10 +1,10 @@ -import { ChartTimes, UserSettings } from "@/types" -import { $alerts, $allSystemsByName, $userSettings } from "./stores" -import { toast } from "@/components/ui/use-toast" import { t } from "@lingui/core/macro" -import { chartTimeData } from "./utils" import PocketBase from "pocketbase" import { basePath } from "@/components/router" +import { toast } from "@/components/ui/use-toast" +import type { ChartTimes, UserSettings } from "@/types" +import { $alerts, $allSystemsByName, $userSettings } from "./stores" +import { chartTimeData } from "./utils" /** PocketBase JS Client */ export const pb = new PocketBase(basePath) @@ -46,7 +46,7 @@ export async function updateUserSettings() { } // create user settings if error fetching existing try { - const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record!.id }) + const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record?.id }) $userSettings.set(createdSettings.settings) } catch (e) { console.error("create settings", e) diff --git a/src/site/src/lib/i18n.ts b/src/site/src/lib/i18n.ts index ac8a2752..c8fcb12b 100644 --- a/src/site/src/lib/i18n.ts +++ b/src/site/src/lib/i18n.ts @@ -1,11 +1,11 @@ -import { $direction } from "./stores" -import { i18n } from "@lingui/core" import type { Messages } from "@lingui/core" +import { i18n } from "@lingui/core" +import { t } from "@lingui/core/macro" +import { detect, fromNavigator, fromStorage } from "@lingui/detect-locale" import languages from "@/lib/languages" -import { detect, fromStorage, fromNavigator } from "@lingui/detect-locale" import { messages as enMessages } from "@/locales/en/en" import { BatteryState } from "./enums" -import { t } from "@lingui/core/macro" +import { $direction } from "./stores" // activates locale function activateLocale(locale: string, messages: Messages = enMessages) { @@ -18,7 +18,7 @@ function activateLocale(locale: string, messages: Messages = enMessages) { // dynamically loads translations for the given locale export async function dynamicActivate(locale: string) { - if (locale == "en") { + if (locale === "en") { activateLocale(locale) } else { try { diff --git a/src/site/src/lib/stores.ts b/src/site/src/lib/stores.ts index 81a4cb65..917ac669 100644 --- a/src/site/src/lib/stores.ts +++ b/src/site/src/lib/stores.ts @@ -1,7 +1,7 @@ -import { atom, computed, map, ReadableAtom } from "nanostores" -import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types" -import { Unit } from "./enums" +import { atom, computed, map, type ReadableAtom } from "nanostores" +import type { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types" import { pb } from "./api" +import { Unit } from "./enums" /** Store if user is authenticated */ export const $authenticated = atom(pb.authStore.isValid) diff --git a/src/site/src/lib/systemsManager.ts b/src/site/src/lib/systemsManager.ts index 4570c193..39afb5d5 100644 --- a/src/site/src/lib/systemsManager.ts +++ b/src/site/src/lib/systemsManager.ts @@ -1,15 +1,15 @@ -import { SystemRecord } from "@/types" -import { PreinitializedMapStore } from "nanostores" +import type { PreinitializedMapStore } from "nanostores" import { pb, verifyAuth } from "@/lib/api" import { - $allSystemsByName, - $upSystems, - $downSystems, - $pausedSystems, $allSystemsById, + $allSystemsByName, + $downSystems, $longestSystemNameLen, + $pausedSystems, + $upSystems, } from "@/lib/stores" -import { updateFavicon, FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED } from "@/lib/utils" +import { FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED, updateFavicon } from "@/lib/utils" +import type { SystemRecord } from "@/types" import { SystemStatus } from "./enums" const COLLECTION = pb.collection("systems") diff --git a/src/site/src/lib/utils.ts b/src/site/src/lib/utils.ts index 134a57cb..e7d42e04 100644 --- a/src/site/src/lib/utils.ts +++ b/src/site/src/lib/utils.ts @@ -1,13 +1,13 @@ import { t } from "@lingui/core/macro" -import { toast } from "@/components/ui/use-toast" import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" -import { $copyContent, $userSettings } from "./stores" -import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types" import { timeDay, timeHour } from "d3-time" import { useEffect, useState } from "react" -import { MeterState, Unit } from "./enums" +import { twMerge } from "tailwind-merge" import { prependBasePath } from "@/components/router" +import { toast } from "@/components/ui/use-toast" +import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types" +import { MeterState, Unit } from "./enums" +import { $copyContent, $userSettings } from "./stores" export const FAVICON_DEFAULT = "favicon.svg" export const FAVICON_GREEN = "favicon-green.svg" @@ -31,7 +31,7 @@ export async function copyToClipboard(content: string) { duration, description: t`Copied to clipboard`, }) - } catch (e: any) { + } catch (e) { $copyContent.set(content) } } @@ -113,7 +113,7 @@ export function toFixedFloat(num: number, digits: number) { return parseFloat((digits === 0 ? Math.ceil(num) : num).toFixed(digits)) } -let decimalFormatters: Map = new Map() +const decimalFormatters: Map = new Map() /** Format number to x decimal places, maintaining trailing zeros */ export function decimalString(num: number, digits = 2) { if (digits === 0) { @@ -131,7 +131,7 @@ export function decimalString(num: number, digits = 2) { } /** Get value from local or session storage */ -function getStorageValue(key: string, defaultValue: any, storageInterface: Storage = localStorage) { +function getStorageValue(key: string, defaultValue: unknown, storageInterface: Storage = localStorage) { const saved = storageInterface?.getItem(key) return saved ? JSON.parse(saved) : defaultValue } @@ -142,6 +142,7 @@ export function useBrowserStorage(key: string, defaultValue: T, storageInterf const [value, setValue] = useState(() => { return getStorageValue(key, defaultValue, storageInterface) }) + // biome-ignore lint/correctness/useExhaustiveDependencies: storageInterface won't change useEffect(() => { storageInterface?.setItem(key, JSON.stringify(value)) }, [key, value]) @@ -155,7 +156,7 @@ export function formatTemperature(celsius: number, unit?: Unit): { value: number unit = $userSettings.get().unitTemp || Unit.Celsius } // need loose equality check due to form data being strings - if (unit == Unit.Fahrenheit) { + if (unit === Unit.Fahrenheit) { return { value: celsius * 1.8 + 32, unit: "°F", @@ -178,7 +179,7 @@ export function formatBytes( if (isMegabytes) size *= 1024 * 1024 // need loose equality check due to form data being strings - if (unit == Unit.Bits) { + if (unit === Unit.Bits) { const bits = size * 8 const suffix = perSecond ? "ps" : "" if (bits < 1000) return { value: bits, unit: `b${suffix}` } @@ -314,6 +315,7 @@ export function getMeterState(value: number): MeterState { return value >= colorCrit ? MeterState.Crit : value >= colorWarn ? MeterState.Warn : MeterState.Good } +// biome-ignore lint/suspicious/noExplicitAny: any is used to allow any function to be passed in export function debounce any>(func: T, wait: number): (...args: Parameters) => void { let timeout: ReturnType return (...args: Parameters) => { @@ -323,8 +325,10 @@ export function debounce any>(func: T, wait: numbe } // Cache for runOnce +// biome-ignore lint/complexity/noBannedTypes: Function is used to allow any function to be passed in const runOnceCache = new WeakMap() /** Run a function only once */ +// biome-ignore lint/suspicious/noExplicitAny: any is used to allow any function to be passed in export function runOnce any>(fn: T): T { return ((...args: Parameters) => { let state = runOnceCache.get(fn) diff --git a/src/site/src/main.tsx b/src/site/src/main.tsx index d241d475..9b444114 100644 --- a/src/site/src/main.tsx +++ b/src/site/src/main.tsx @@ -1,21 +1,21 @@ import "./index.css" -// import { Suspense, lazy, useEffect, StrictMode } from "react" -import { Suspense, lazy, memo, useEffect } from "react" -import ReactDOM from "react-dom/client" -import { ThemeProvider } from "./components/theme-provider.tsx" -import { DirectionProvider } from "@radix-ui/react-direction" -import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts" -import { pb, updateUserSettings } from "./lib/api.ts" -import * as systemsManager from "./lib/systemsManager.ts" -import { useStore } from "@nanostores/react" -import { Toaster } from "./components/ui/toaster.tsx" -import { $router } from "./components/router.tsx" -import Navbar from "./components/navbar.tsx" -import { I18nProvider } from "@lingui/react" import { i18n } from "@lingui/core" -import { getLocale, dynamicActivate } from "./lib/i18n" -import { alertManager } from "./lib/alerts" -import Settings from "./components/routes/settings/layout.tsx" +import { I18nProvider } from "@lingui/react" +import { useStore } from "@nanostores/react" +import { DirectionProvider } from "@radix-ui/react-direction" +// import { Suspense, lazy, useEffect, StrictMode } from "react" +import { lazy, memo, Suspense, useEffect } from "react" +import ReactDOM from "react-dom/client" +import Navbar from "@/components/navbar.tsx" +import { $router } from "@/components/router.tsx" +import Settings from "@/components/routes/settings/layout.tsx" +import { ThemeProvider } from "@/components/theme-provider.tsx" +import { Toaster } from "@/components/ui/toaster.tsx" +import { alertManager } from "@/lib/alerts" +import { pb, updateUserSettings } from "@/lib/api.ts" +import { dynamicActivate, getLocale } from "@/lib/i18n" +import { $authenticated, $copyContent, $direction, $publicKey } from "@/lib/stores.ts" +import * as systemsManager from "@/lib/systemsManager.ts" const LoginPage = lazy(() => import("@/components/login/login.tsx")) const Home = lazy(() => import("@/components/routes/home.tsx")) @@ -114,7 +114,7 @@ const I18nApp = () => { ) } -ReactDOM.createRoot(document.getElementById("app")!).render( +ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render( // strict mode in dev mounts / unmounts components twice // and breaks the clipboard dialog //