From eacbb14279b6333979bd70a571ed6cf2a48d59a5 Mon Sep 17 00:00:00 2001 From: Andrejs Baranovskis Date: Tue, 19 Nov 2024 17:58:55 +0100 Subject: [PATCH] Added docker widget with documentation :sparkles: --- README.md | 1 + docs/configuration.md | 65 +++++++++++++++++++++ docs/images/docker-widget-preview.png | Bin 0 -> 37217 bytes go.mod | 18 ++++++ go.sum | 64 +++++++++++++++++++++ internal/assets/static/main.css | 31 ++++++++++ internal/assets/templates.go | 1 + internal/assets/templates/docker.html | 44 +++++++++++++++ internal/feed/docker.go | 69 +++++++++++++++++++++++ internal/widget/docker.go | 78 ++++++++++++++++++++++++++ internal/widget/fields.go | 21 ++++--- internal/widget/widget.go | 2 + 12 files changed, 385 insertions(+), 9 deletions(-) create mode 100644 docs/images/docker-widget-preview.png create mode 100644 internal/assets/templates/docker.html create mode 100644 internal/feed/docker.go create mode 100644 internal/widget/docker.go diff --git a/README.md b/README.md index 0e8cfb4..3519a03 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ * Repository overview * Site monitor * Search box +* Docker #### Themeable ![multiple color schemes example](docs/images/themes-example.png) diff --git a/docs/configuration.md b/docs/configuration.md index 832d035..606d4b5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -32,6 +32,7 @@ - [Twitch Top Games](#twitch-top-games) - [iframe](#iframe) - [HTML](#html) + - [Docker](#docker) ## Intro Configuration is done via a single YAML file and a server restart is required in order for any changes to take effect. Trying to start the server with an invalid config file will result in an error. @@ -1776,3 +1777,67 @@ Example: ``` Note the use of `|` after `source:`, this allows you to insert a multi-line string. + +### Docker +The Docker widget allows you to monitor your Docker containers. +To enable this feature, ensure that your setup provides access to the **docker.sock** file. + +Add the following to your `docker-compose` or `docker run` command to enable the Docker widget: + +**Docker Example:** +```bash +docker run -d -p 8080:8080 \ + -v ./glance.yml:/app/glance.yml \ + -v /etc/timezone:/etc/timezone:ro \ + -v /etc/localtime:/etc/localtime:ro \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + glanceapp/glance +``` + +**Docker Compose Example:** +```yaml +services: + glance: + image: glanceapp/glance + volumes: + - ./glance.yml:/app/glance.yml + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - 8080:8080 + restart: unless-stopped +``` + +#### Configuration +To integrate the Docker widget into your dashboard, include the following snippet in your `glance.yml` file: + +```yaml +- type: docker + cache: 1m +``` + +#### Leveraging Container Labels +You can use container labels to control visibility, URLs, icons, and titles within the Docker widget. Add the following labels to your container configuration for enhanced customization: + +```yaml +labels: + - "glance.enable=true" # Enable or disable visibility of the container (default: true) + - "glance.title=Glance" # Optional friendly name (defaults to container name) + - "glance.url=https://app.example.com" # Optional URL associated with the container + - "glance.iconUrl=si:docker" # Optional URL to an image which will be used as the icon for the site + +``` + +**Default Values:** + +| Name | Default | +|----------------|------------| +| glance.enable | true | +| glance.title | Container name | +| glance.url | (none) | +| glance.iconUrl | si:docker | + +Preview: + +![](images/docker-widget-preview.png) diff --git a/docs/images/docker-widget-preview.png b/docs/images/docker-widget-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..5b644d47a4485a1eafb59befedb9f0f541743c3c GIT binary patch literal 37217 zcmce7WmFvBwr2>z-5t`n)3`%$f(8jRPUFGdCAfQl#x(?YcNz^&65Jbi4KBmG@BZ(a zkF(~jnGaoEXVp2o>TLPdt}T(DROB#FNm1Xtd4us$URwRl8~EDS^EG6|*W-I$&6C$Z zI2Uy}$v0IK$5^vtr#-lxg5njhAj`BJ#Z{A?`{nrm}$f3mS&6|YlkJ1vF zo`%OONWRLuWJqU5*Q-*g@Aar7iIrpKt>c+L`)D@)<yx_acjMn$2d75i< zl%NQ7k~;H`)T1vQyE&Q5WF-8FZku=4E{7F9&VzormtDJllRo%LN@HtZu_ciKA+Lu- z_$X-if1Ui-NWM}o6!h;40Ow%T+J7!=ArhFN{A)fSWEg|FANQZtWSS9l|DHt6UxKap z-$Vh@_MZT$|0Wu-oF~cp@3q*i691DM{Oi$?`(~>P9 zL}2ZB0ai#0Y5Xk;snyL2Vhn$4gy3&}>WXu-il<&O*+&tS$TZY0n#Sy`llyE z|LreA?`Ou$+Cw1UeMbtI8~Ce z{?wu&WZ@4&Q`w}W_Y>zkr_px?w+IZmjL@o&!eBTe0bQH-SAVR@FLq~fya-u@Ya38w*cV#*>*=%~$eL z?I%xIoRW)prR!N3DZxjbD~ex?^Y1N{VxGzksmQM0g6G64$W9`b+vUz3z``bh0HNm^ zto9c3{#+_=)cSX_6qcx$O{P!ys*Lx?P@`R18<&6dRI~A>5IfTqty?uW^!u(!-u?lC z0|D4OusJuz5i#KowYVVO9uyaSjxW&NSPbuYL7>- zRwkHITm?9(gqVs`M)OznB*28+6;{Ljw3Cci{jnEQYA0~-DEelIEUZ?_D}jOkXq8*( zxnxC98=#`%2)sRomW$5qzvmN`KE`kH== zJ#oc7$kg+`KTIb*dT^I94Yd)tVNA)D{WsPW9WX&4kZzo?e*~YPd;GLFANda}vp@V# zAm{%VrfnBgRJ^65qhn-dM$!29&O#EBws&^{92{7!t%A^R7v-M-S~4=REIrK50Rr)B z$p4ss5o-mmrjAY|%kT~j!)_z6(RXAe3#18O9&vPJ%iQxHuqI)Vs;i@A(Wt94m-*I? zJ(_`CPx>FYFQNveC#A5l6fB2=w4M}sYyP7PQ7KF;i`}??Rh0hUqWk|btC@m}i&KHp z$bd$w0ai(Qga3<{R%L`LAN70sx10n1k2QCA;8s%OX-iHOQIwQ$k*7CUW4}`e`H|4j?5Obzj+H5aa|Jy}QNAG^955Ur%iv-^wiYnjmE9t8Vj_oh{Yd^$MhawQ{O$A) zZSegSM$6+bRCq$W9(4{doTPkXp`@*h^ji37dc8oOSZ(_75SiH^>ZT<-;Jq%WYOzs5 zikq%~Wi=LIsej1@trxod_Qf9oYxKTm{|@a0B^en|q=~YxzcKmluTE)WC4mx6A$IBa zQDrzGl0&%keff2wipp!4OMDoxem^>w=?v#$6&WQ`QffD6dFlg?3%ZO|NrqL}m{39Z z4_@a}+x`fr@>;v4Nn$=n;kU+E)o>?Ju8@QS+0gkiTAp$}K|8FI$jrRurQk2>9w)S! zCDQMiMc#BB%6@1|j6%2{6G!gGR^#4?NFcj)&=R({zB^M6Mav)=T|DZ4yGH371WE9_ zZR!X}z*hIjQj|vhmS6T0V`(N>Z3M036ZcmVe(Q=#xFT8@d3RpK_3e1e&!NUoA^J;E zC^FWptoM%*g*QPs128pB;0eVOj$c$iaWx9r#zTo?x?Goy+I*1eAK#NdJ8O;%LDA73 z{(ZNnSTH6ZP1uAtI$m}8-X_&m%R9hn3T^iXGeI9%@_CM9-1$%}IvBBUDBPGFUz9=3 z_)$DC!mES8^p?}Ws^o1ox4POVIh;;xD&1MR_Wft*J13V_4$hlBcd;3)SA)MaVBIc6FUs7w7T6chd6Kp%+)_g-Q36e#m^)9~(TppzklUwkL3#An z1qIPV4_e^+GaZlsZZ4kxciyxu24Fn{)ykrYVsFHKD|t-$RguQ^B8OXMlK;)~nnd^1!fG zwkb^`vdF}ngl{HcVfv9%KS$m2mqOO6=!ke%Pf5(D3;F;%kC&80Eo z0Hb$%+(jJZhx^0h7K-r3+?IM~NkE5$TJ#Rluo!+3(PCW>k0FaF_4U2bB0;?MNJa?H z$Rwphyc6e=X-*|4jpsBfc9u9UW;a>-Dy}3y$RQ z(lxnwl(2`&`ZsBPT;Z-$A?VEVlnUcO-2N~0dnXS^N$S(Ey_MSzFEZcTap$9Md<2>B zs@t7-sd>=WLP(3Tcw~nzDi;W(p-wRPxfQ(bl@Je{xP8ATn3vDS9yrpaq=iV7FmQ4Q z5Ktke-x8Gwoj>5~W*8guHW3qt#SJLZ=f~O;U*liVi;xP#-=(X^w6n1d57$mc*?;yN z9d8hxbqv^ip9a=M6Ny3)W-^sWhRf-c|C*T25c4+)8xLKY?mP5P8D53bCXUWe13Z3% z4&=3_X^FiYolrJKcQM=@T*-)|f|p0cPH*L23$Xr3E87`{y3{4s5cF;E`f*wO@aV&P zoiKt(*&gw7POudNk6j}+(XX+yjj4}Nkp*6o04Zv5hO!n$g;aB7uQfG5ldM~KdnYb5 zo*nuvXfFXag$mg6OHIs1qcfM< z`!e8^F0$@yMjn$zo*%9x-$-BePITos5C0_iPQx9)iIs_609SHY_)Nma- z;8d8ofA+1y2&|Z%Jep%;@e?uO*3M4vd_Qp@EQb6BMG?POV;S3^ z_~}EZ7p|n^dumFc4JuZ)Ye&q^g%ii`M4K;g+(qvt>TFZP(W{{5!$2uTu`YB)n7~2s`lQj8Bv`KE4)0Fd3N)`Ne6Ga$} zZHsP5s?O?*^_)_{rl6=*T}|qc;iqvkm%e4L!QxQlGdV%1WVj1lHgT6B}i$_F2_Wuag; zD7Dxe{Q2U6)Z_d9P8s2s-m`*UH9~hbG?>091_I&jCspE8b>H<@>JA0`n}4$P=jTB* z7dz9`PhV|^_Vq|=C_3Q84(Uy|hJS)~>*0d!xE&9#+`%M?@1FQ>?RIaVTzDImnK3=( z;UTTzH7~zhh+WflYH|ql^@Uq3DY}kXS*bU5=Ve>>h53(s+b| z3=Ak5?3NHIgc^dw!oq-`4ev~!m$%O%fzs`hJ0|-_a5Zh6lt`u_3qgCIlYw4+G$qZX zfnJKcCX&@*5#p`yk{LeocqH~tA?VeZB0Y>r%bE7HY$}0$(FoBA2$>Jo5J3L)C>UZn zfSFEVz?SH@b5g;IDN;OkZ^I*J+S@MaBs}c?=akP^q)Lzul)wW+VnGMao!F}&?{#{m zoSNcw(U_Z^mk=XgYVaR>bfTNCp%3N4x1Cmw-v*{~QDMurVrPv*wqW0(Al1-NWWZ^& z)lu6}Td!|i`9suBWkIbqbQg4-iMS;8yT)~&=gMgWx`Ls>nLpf)ba_fS@Hsgj)X&D< z^BD!Jc^v9daC3DQrymqgTjscmL7A(|A* z%4}w49k_$9E~|SX#A9uJ zFg(T@(2-Q0F&{7fEKiI(Jv}Y^@(jCi`0wb3x(Z=#L3TSS)PcO}16(S$I^I?RwvB^B zcqm#xQ0(|pws%o+F{)GuwmLuW7g!J+{QV`B!)X&#Qxdr}dyCCN2i*?Mqw&Yt3hP~$ zjikJtR(U2K87ZmBa6<3dl`WGJ)7AZ7o7>y0oo}k-#`P$&^Pk|1qP2mtZMl}=BZUY&ydM}fAtfc0M$Xp}*8g#9OTLq}{LNvh=Xwv^-f}A* zJ^{hm?Kz(9QWIWcVj>jxbz7Xg2IU(^%fu=wagUC^zHiwL+QzW(B;}nLEpQ~aWMphi zY?J{_YK39Mg3j-3?Z{9xzCJ$kw>b6^JzO8ld3(13KW7vaP#Za#^ET`1WmFG&t126(9ll6%c60yp<4 z=>(H}pOjS2Zl~=QR%1GJP0Ve9FP@e2j2ezb)jO46-~p-AjmhM9ATT2i4$cKXKgv8h zsu{Go$|loK+&FK!f)R~6enq%1ooJ4 zxy!2v>}Aku6?AolE7%(YCVj3EF zz}ERH*+Ry%6(o-o-LsXQd4+{EV{rCs*L@D%l|#=@NVgetVHji=<1r+&eNI1AC|d0% zH{qE9(qc73@Pq!pP%nR6Ly?rpdd8%Q%F0F_!!ep2>j%G{oGd3ftiS9XXBwsi4s@1{ z8P}#VQc^14Z5$nui+JI_)1IS$6lgyuBp|>*I)OoJy5M~MIL`ZIlygK94rnWe3 z76VhT)w3Bb)W)c#4H=mk*UNV(L92~4x-JMHGzz;CCYEz5z9VzhZxlsq5N$MF3$&etRO$!R2B{>h5 zWwv&-?K_g%Gv>j4VP~D#(XaG2=4&^7>%S#|K%hLSaCayu|Mh`k1JT4_B~r91d;62A z>1o2^H@douk89K+f9w@4{w51@5A&2cplg^?=y!c?tz=_v*9!idCuTax^UR5Q9T^b; z{gI0a~GhfrC@E53Re1rBSgj{-jL`u_aEr;$b9>>dqUxU@=5B~*_$ zukkFct{!?}5tkd->`EL8I$P2!$WLrHht$^zzW+XR?4=qohFtW&;ew0-&c`gStzEl50UdDH@HMNBm)q$SWR#c`#kBEvCiSt=Dt`KV!HFfuT81~k ziW8+%bl)I1XK(*Wv6~?VPsht?yQ)b)gQZ&Rx=A10PrlF%m3 z$>!Qv_x9&GQ+8$SbAIQ`_M7Pb^IS@zxFm%DZ0_0<9%^Y{C5EZ`1tr{ z_x8%(f0?r#{287*eDBn)vsP^JuuxSswB{VI=62{03Fl4{j*J;177S;kH2?b9d4i>*CjhKhH9EjB4i^*JS*VjlEo zxm)1yba~)@X&@~H)Iq8_;!&;L@3F~kl^8igPg8eNqxkW zMJ6(i$Fl?}x?j4&UJRYs^;?FA=G*cVyRWDGY7`@U>T6fEA%KX*8w%N2@?hRm>|53) z{=n^Mq!xU7r3ADewHA-qF&Uj0YEXQ2zkfmH%;Zusi<0O?-*(wyUxkWEwqRo;dn+Md z28*yv+L^URv50a)xq79SDr+BVMMX2L095TE?0zKa>3MEfqchpp7ml?ZY^|o4R@D=X z^h-Hmjih~)rvlQB{V}c9@t1Cm>@U%sBZ>HidgNNWc*3x#;kd5Azv3@ZdOvL?Fy^l^ z;v%Vpd!;|)czpr4G`L~#8E}kw6MJNZF3>Eu+$tK>-gPoP5z_r=+>B( z^}>_mVX3dbxrestW6|CX>$J@Y?$xCDR~@t)spXcT&1muS%4SkZyF1M2$&MBM%(ZUF zcSu92m2xOu$DlHl@ic5oZA8xf(e$y|ohdHVaf!XpDcFg_m-|73XVXIjVZ)LS{9KiZ zJ-}eUf>#XVR#miOgu7daJX`8Q?#{6sQf@G!h??zkq&)R~AWmFVHqyom=Z_~EjPekE zPp&~k-akSmCh5`)#gyw8!qSP(${O*?DXSCuxX@I2Pibx}+OfN{voT#L=Xu}2bs|(t zI^R36Y@iAa4K)JeDfS#qoRf2lT&c-ip3F}9CWkQ~rGk5V846@#g-w^Y0B80^@ZTS8 z`^80q8d+XsA_U;ot?!ALIIragh$8K9O)q`=iTX(i=6-_Cvqj&g`h_d2*Un%{1?aqs zM-qp%AKQM!V3)NI9AYDsYcpNk;hYk`=iNEtAA)=~lGs;0Ipr}qR0&qCVQcd?O%N&iXI5a)bM`R!Hpj3 z%d7DdNzEq8=DuFx^NLrS9Kb{o^Es>7#%}nH3OzXu=s$nQ5Cgi#TYlSLo1JJF9yRVb zmh}tc`hHnZiv5@5r0L3|a?7Mqi1aS2|Bc_g{}Be-w*LO+;5R1>7EuR>$hft!!3Yc4 zPZNHDmoK4E(+He6tla342`Fhv`6hH20G(g*Z*h!#@sL1rcwU1wEH@A=r2OgCkP&t- zJI?@xiB2sw+7QK%3+h9t$ET;^&UzEJh0ze*xJTK;=P1Z`QLWUzh%9g(KgDH zi?{!zwt;JHe*UDPiRQ@oZIoqY&^uq&cn22nUWcUY^cS{Tz*9u}WpkfVC&3^(fe_G< zG~59(^S#+2?z7L~$(Q^UOPXn2p0o#w_`yHZsnAENvt2u0*YvAcU@jf?Y>djcFHWTN z_N#jH9g`lNHKMi;BgV@%Z^f#pqMicq35`iGFnCXf@)7^Ss8rb#72-L7z1&p0T6K1y zpy0?@;O%9v{;#cXUdu;7&x21q$P2Nd%U7o_9Y{0mcxU40f#<|AiH2n@ohnzI1V4Un z@R^`Xe`Lk>A*$ccbQDEF7Nf1me88_P1vDBtO{#9Y^wyouW3)T~ahXcfK`lG9V#mKL z=EvR_<`+|=&rnvF%e%GsKha;GiVt_U*Z$%P7N99wFh!Q-s*2tm)2sYI>P+(W67>9J z%PO20A`35DDE@LAgXrxmh?#SjR+HrCu)#^Y=od1HG=ILvEcz5piDz6Jx01(w1T={7 z!5xnrI1gRx;&Mzcsm=8PoWl5QQ|1uP-R zQd==LGp0)jI?K^p`i|eVxiv2L*&q2hu5&6UmnqIqzoPwFi#=Ww@6{TMtxZxl-zlWFDq&f~p%3{E=dS)0F8sx^@6kT6hOJ7vO@sr?-_mZedIbIr9G7 z3Nl5~crxohTY&Q2nwcqBifqW^jnK)lqCBB;~3eX@ob0`iu_QjArwgUHN zQJEg^oS@-;Ew|$Yr5p(T(B(=B4=sdMwiY*$Ubil6+aSY2?_QxD$X?d?TI7(o0jF=m zR@0ZlQaM8RiKjqeOSli*KR2~xDVt)}G8Kku_2Kxa`2Baggm17{Jp}?;4rh_IG|Kz^ z6bkdzFVD+f3n`957mn^`aA?9VI)8Br3bqRPT#LLU{wY>w@n29CBN_I1)9Add-eAkL zvJ7#%YVf)wq-BU^->J}s0i+PoCWqa(estEyFiOiJ(r}4plKqsVCbP&TI#=gK{ON&d zQZ3FeI*h~spOj|~zg%mty`v7|W!+%({!Y4X5qys`S8E^=Fe(#b z1#;<)8^^b#jV7E$FVi2;~3X}3mw0-V+GA$ksUe$q~ZA{pn`nAg56~W z9_=H#cDsFq2IC9@4}FD3q({*BL?iXM6EfLfjgMAq6j9=VCu?e~59>mewWq$6%z@4Jmxdf2M6F(tuQ7hL5?8kk02zFkj>9!3HY z4hBK6_or$8Fn@0mRC@vo@!mwX-PZ0?thGvQ>|$#~VLTcvdW0+UafG{|8tYI6ZG3uX2Lyqp#UcT{3M(XFL- z9P45w1`S+R(~t}?Qc!YoZjv?Zi41}`!6L+y4woF&zUkTMb8$7ewutZ&6&lLWNRA`_ zZH3uwDD#`{eX*qOkFU&p=z6gjBN2XEUTss8PCc!~LPX^l#vxi6up=p7wj zb%ls+Ak4(hMPO-*X1(NA$nlNb32k9l=av%_-HZMQcso4-$Fwj#0VCQ7IhVDKNf`y6 zlgnKO6a|HXdR+H?I75hVVWVSsjkyMyJw>xqBo%jfU=_6q{AAPwNpsFjGNS2(q0=#c zUv_CkFpwf{4}YN;l9*!pZdneugo6Vx5<;6IYWeNCCn`S^p@r+_Ze9n z8LgYqI|bSn1PtL1_9KULP|$d3tt6`t({*)s)w)j}cf#KGL4Tyi?e$XWe4Cr_3wUyL zwN>NR@ikzD>V0$a)8h>tlh8c^A)7ALxy+Xqtt$Na!(tWeVHUI*Bny7Fy5$C>Z%QV*;yx-tTNQnaM| zQ$Y(jNx7s?0qcz|)P_E^3$+Az22V*VCE<0f8~{dMg8sOC|A}+{M%$6M$|2~{vR9su zFFZLN&K1uDO>fQ2Qes~D1dT-BmmT*v-8TMDkq(z-THaU_t%1*WVpXLWeJ83u?4WbOlhdW{}i-hj5 zyDJ1-*Vdr&PQgEhh%^!l__NHiQ==*&dqm!-Y4G?PcYn1WYc1&fA)Exk87`r%S=5#4 z3e<{}O@Bb;2$Mgd9<7sEupD?6rWe-YUnp#(F58o23?V37sKVjxYCDb&lh)cxdFG}B zB5gEXc%)x1n+pv2%mUtdV*b zW0!YEl<7WBPV};zXe1pbrhx1{k$ANto*pkJjjgQS$JDkb2>SNA%vA*>WattD<_}%8 zstu#fN43tg5%33AS=!A<`JUrMeB8Bum1_Lpbb|3Cqk1zdZTup*;U9{BQ6erEQ~RO4 zkrq&O)|VO_R`5fI=Q)$3N`|fYPVcNXClm>-XCY*|;y7&B5_-i|V;_7=p-Io_+V*0C)bUp$tc6=xY{ zyI$FzPu2zS{vs2eiwR1PtO9P^g{!O)g;^8LG8ud}ar*X`>XVOtuXuL_^vkpN97UmA z3miWme$jDrW_N1RGYk9+ao^-T)fFN;g;-cEu=PWqyCZi1T)-MNj^I6=ddy*d_IDu@ z8cJ&V_)+}D3`eiUnr3Tf7TC%reJKW`MT$}EP=)h$BkiUR32QjmbJ9EFUUm7-V%sBv zX@Oei>JHD>iB%Bp=b~&)wL{RHnk6=lC2~0y8#v=ka*v7*FD+FSx+N2^+5P+GjCzUu zm|=3j%wgdoh-(|H7~7Z>Ux=Lh*D}P*A$d$Zy0pPAY|t}^tbqBCGjdq<3mT}+qGh933;O#H%YYyydEy*UqEeRwEOJZs~0TW zh_kD5bOFsmGa$hAoy&f^eMOkt$hV(0jg17w^6ddHVqFjR^v_QoR76+O#}Xiy%~w%x zF1MmRda6}ir-;fOuD@Ry+?b#c2bWm1EI?=mPTyf7p){84-+3NJ8i?9iA@gD+COYX4 zm+JdsoWzRP+~jcSUbE11YXinNtSX}tE(0#dO<%J>DxdE%q^zX$q&n7m<&&13lodhy z<}_D3666c*gyj~wAFyqp>W`{n!>umbK?)Vwz$unHI%;iu@xBy202Fi#J3Zw<{~Vhw z>;YgZO(hJ-Is#Xc#$7Ij(%UXG=;iNGl&Jp-_--7}d29N@XVg)1@)aOszb@!@{?%*V z?iPQ%NOpimXY%dI-#YBSJa0H72hYxc(voHfXq3!JX{#d!js@XU}C^LyD-g4SLSD%V(RZuSL8XW6RHS#vqD6>b+juHQtOh11-u^67gE$ zKZfz--D)wF#n^zn{2l1Mmlw+>ekK5yne+MFYwGdD6798(diWm&UrI4v<8bZPGo;Tm z{OJ`>A$j0F#M|?sh1-Mjb080Eu@~I#o*GpQ*$tg+9Qi0e|HPQ@14CX}Sya5(LHPD) z+S%lERINwK)9t2j*G`jqktL1Z68Z$DdrcioOqOoVJOv;7;;r}iy>Wu@gCPq#=^>p# z1Uj-r$TSkMSP23D8pUxpB;Ncoyf7Wu?#haTx>Pt`Cvl)(W3m~WccT?+PS0*97`!`c z0BjqkXqjEJg|mr!(g}6?97uXe=dLhYnrfVkB&Ryv6pQh^v!rO{%!{io*o-BdEVoX5{Ng+ zK)ZRhh7*&U%;9J%?c4WaY6tJq4!m4(*Sr+$26R*3z0*{V9sRDT?fC5Lc4d+pSYc}i z5@<*JGKQN0%~BWjkJ??pm^!b|@m+SbdZF+p61n>ME%A9YuV7Aop3;t8>N~OUmbARSo@J}E zIutmWxIcx0w^VOBIPe%qITdh?Ja^m5H8_r2i(YDF6#J)Dv8|3zob}QRkP>B*dQ>K6 zfk6EjDz@By6DXL5HD!AbSf;DzIsG=rEY;&8z2CY?yXLF;h+JU ziNYF5bgiv32js#99vkOJocG_0MB;HaG6F0#7M7yH=-hROSkB9U!J9tmC5!DdrVRdd z=MWMm=;N=H^zf7`t%}azo`gB8MiIG#^J?v)>O-B>7s%AWW2$LcNt4kv@ZviK~yIycpDgpi9^VuD06jlt6%0kYp{ln=xjlgOsIzXM20& z_BV zm#mofhcV`SiRa3dmtNIo?wyNQ*8$pPw0ME_xE?myVtR4e5GUW^^>Qo?f;piL<`4*< zeQHK4cK*E^=O%M~_xILdPbk~Zp*@#$QJKKcK_!5@|Mn>85BJTo(DligjMnIRfm>R` znlyV+k-5p^*ox;Bk1RyJPFTNqqg==5jJdF^@40=U(dPBL#A_l;FkCsa?C>)&Lyfru zaO}a4qlmNWjBbpH;->Q$?&}aTyFF`91C|l2xZ7+9+q~$7YaTO$-By)jUV_TdC=%9R zAu%w+ZU&pZu1Dr|h)?Vcp%lx~?HdP@`|m~_j>XM%SxX3WBUW_9n4>l}>Qq)(*{4@M zNAWTk_Ij}jTNrIh9W&he9G?V1>y(`8r42%eyOE8&_Tk+-LpI+ZzG`)(NvdrbgN5jz z_>ynpw3$HaQzz14I6NRpr^jQ&CIw*4;C|Z=ZMflIZVP}~rargaU(@_1U?9+-&+ne* zw$*8+fUQKwOa?YtzoiB}GxAU_EUw#_%ShLP8Yz|>*tN#?5$YkoUt^VSI^SM(`NF-T z^Zr37OWva&l!}zKh$gG*Zn7;7vtJ!8{`H-SoQrZpNL!$aSS{IPOMD=kJ86z6H9#a* zIx|pm)ty{-3(5F_@aL}mZye&1Xz(SG@rkAV*ER8Z^-mP~gS-_nYkCuR^7EK5!GMUFHV_%Kio0%q2 zHmJBY6i?4u_GPCbjtgcu-8{%Hvx(EzIn~EE<==bF_pro#Cb#){akHl9^mLB_MlEHG zN69?1>#T=dnSwWx5~2zqpF2n+b(raa{D`?@bL?cO0DDJtJ4#{j{5UhGZ=zT!RM;lnrAT-iD9e0!%>@q~msk1XzxtPWrO z_}A{6Kvly+hblzH_O(|s9* zcz3(6%X~-(^=aH|Hx4fW0o`j-uYav5$}=%0E9*i`Rl`3e8(=nH4h5+m5)A0pkc)Vh zf8NavXkE_=^nbd~(`&}cuh5^b_HcDYZ?av88j8-P9c%Q*?O*#gY?Vt~LP4^xB@2EJ*Rqp2E1^uGb#--lRaNXU zk%KRP!Js_p9j4LF`)y-`k27Bw`a-Ml5Ghc{(evX2Sg7Q7}wXY@pUu6N& zLOKxy+`PQK34q)JFBdGZ0$>*K=e0)UY%KsOA;<7@H3EG|$7C6el>95rP!mFcwBet9 z$qGnvGR>No#?!slqks)phua~hw9L6$PjW z;1+j{kf^9Sppt^*Kg|#FD@FDQH!4!oKZ4Ol{M()E@~|E-rKXybkMG^_$%ch!a8^oX6+xP=8LBk9{JC*YCArB#pq^>h;4>&xGK)4=(0FnLcC ztcx*yBR#326%jy+aBbGm_3+-6D|+DfywddJg*q_MWys&^_yo~%mmc^tURv6OWKy4K z0a3DyG}=c{@XGs9cGYz1XCdbi`SPnA-t}c!Nq1GWX_jnGp{*=I0LMRFico%Aqb_-9 zfA{g6=qArKfY+5|IwKkRTjh&#pmjX`y^udEdwIjQGInbZxhT!)OUf#uq=GDGbl(N4 zsBnI5Hd>mtP1Im4`WZ79c_}X>)NfZLxoBh}UIHg`yU{h>MdJTTOhh6ZjLZ4(0B*)cP!Dh#1=j&MT*Wo6F!tlOBPE zB3c6Php&V65SW>zR`2u{>jlkeo*(dvrE1bkE&;&lZLeuQF6Q!G)_*5e|iq(MQ?2JV8;11>Q#WkeF2uTk~P)H^SiS{+LLsAK|+JxuWWgaX4DfpCn zR`+p$?F2+2R)ZCfpV3=i+XU2z)8PvahWhDulIaz_XWZJ1cYN?+A>ga*G-e$urEpGk zyg`LFW^I&kC{e4I&^JlOO!X|hDTNx`E{jO<)Tv30{bnptTtVRu@|j!(Mq^3artYz~ zg*U=wtt>$sBhk?7asbrn!l{qh74*Q`=QotfnSHHY%rMD98IR}#j}N)mmFru-$eXMa zaXsee4T`jCX6;aKcG#x-SBZ5drFyl7toaSv{bTs5*lRdvf}QXzOGGL?V*~NsSmx)D zs6*QA4=mVA;y?Mf-?o3n+^KPQ2rX95DCoLC3mG(z=+b^m5b8_Yfh^%`9)$7+8Tz}Z$#lsKdd0c~bkFfGi zv@FtgD9?*v4nvB!QPLj?&2oUrR1bVxsid;8z1S70;0L;}?{i7NwZAy!_d}4$Jg@iT zEL141Ov(d^a6Rr6pD~F@N!?^`;_Noe9FUYd5v?pP>YS}b1k@kcy)YQr{L`dAqsYff znwcP8oFVGy&RP|uF)s&lkSrleWy7vJw@AMw*(Au!F_5j?JK9V~>E+TiXygKf*y4o~ zuB}!&iF5@bt!pY8ak1~HW0$n3IT$URqPDbYFRiOFM5k(9v^@oQ0}GRBu8cydqdrfk zYPIVqv@u%gZ0sR2l#&|pf|?$Y4MZ|~R|nsrE|5-*<87y}>d@^D<-Oga6Ol6$iY9N> ze;z`?z|T$Vs!YkI+T8W~>TPXr^(=wL{WDm z?)g2;+up)+Jl>{fq+VPopeLLA#8(K!2mpwLt^G*aEJY-3qzs8;TQ)vjj~krTm5+<* zXSv>F6u_)tiy}2gSi+zb-o}Mnbu_pE{{jP7aW5awNdKl` zUms3fX(3i$UUWMXx)LWBY@pCKc@0>xXy3!EcFRQS99Ool7;@I;T4) zv0dG_gDQZP)S3hZj-fNZ$h)G@m#~bD2H7SXAVU#2uqa7U29Wbwd=g!Ls$<7|*OIw@ zFQ?NuyEZVTv{;^j3Ws6g9+O&n0V9@8Cb=>@`&`JF0n=%BeX3P>c#0F@Gd>=a3*Q~6 zG5H}3Jy6v^P!^xa_c6hSgMuh3J`&K1od|%);V`23vCE zTY5q{m}(oS6$Pd`(~pp#0&3@aMUJB~{<=W0R>oc}fw^)=WNXMKQj5!=-9u*uy_JcE ztkcJ86moRvx{onY^Gds;r~+P?Usc?>s6a$<5fqaS#0T$T)`SCLE zAtUTtXGF4T946{w055T=~pDjPFYQbn9(=#U| z8`?FI@E=_7x+;o@)%1s>B-B$~^%*(oQwwfSOC)s!;lGr!((h0+R~^Y0to6XjW*kg4 zI0iqpfBRKuBzeUcz^?wQ-YjM!93{ht`_R+%P+T{ zDDaY=hQxM=xySi|XL3}XWN{Jkkze5(X}G4jUW4r%$+!V|9%TT0d}!X!xZol>OQ^iH7%D{iwW2HptWneqQ#?0;f8om)ZJnba$a8hZrz>OE8 zSv@ZtV7j(Jwmgu-@`mo)uQ4UeBIyu7ESlim7jT3;4!stc3~}3yluhSQi{sswTHcA7 zgU`UnN9lAZzuKN5a!`)y#&eI%##e==EHOBL??g9Oj1`fZ+n5=yj`xVB5 zm%6m5DZE)x>hmLs#+nPV?jN7fEr@Z52>OPNScxE~*Qq%YOriyoT%Rpu$DTmZC`Hkk zjAv@9;OJ;Pvq(?ir@7{JZU>Dwdl~*hYwOfB5$~x+i6vrZj2XUkR}iEGUDVFa%px^q ze*?9q>S=nB_FbAE74=^Omnc?tG7vNf%a;`ROJft3V@0O0a)P@jcq73e=hmTsEG0ih z)8DNX-WCQ-#%}$A_&HMBky%fNh>wn)E?&dAZQUfkjF%MUTy?IM$eRZ*=c9LWyofn( z$z`B=Ls#7&kbjJxTw&AGw7?CiuY*k2Wo+YO5|!ddg`lX9etXE9=HOFHbG<OE)3cwol!Do!&2{tEG3n#C|)&R&5vP2pC`z2@}y=rfZw4dH=J4Xj-+7o zjIWA3KP@sQ<0Pj$wID8=S^6UI%$0TrRFK|D3hjd>rYF3U$>ck`utJs7gYW3@L&TC+ zy-8cVCTS6IB$|^qh0_oTJD1Kfh-;FQHV3VsVMfH(v-V28#kRYN?Sp_MN9m}A0;menrJ+R z*a`U+1og1{(z8wCeDayNmlZ=dU#E`hJ0dWwZ#8Z<+33+|QZH?ZYa64g__`6pkbD%) zYY|Xxa*1nPN%{}{JM<`>5Vw{7dw4}d&e3aSQ zNzedt0=n}to$wN`y&7Nb_u$%($BTVXNvoY3%`aM5no>r=IHxB$`9sDrM56YSHJu0s zB8F1lnPQ=X;r+&xre2$rVWR{1uJ$kMh(6kRr)L34j5c=6K!4K(FZ7-+ap6ZTNsr6| zq_V=(>5TL40hA+qt}U?4O$b6PDpno0*cC$HOw7#)9HUmbU87@A|FK&;s|r9ynm&4G zFK{3#GWY3JZag5%Q_oF1e{>DK)*^s9=(>4ju)57xfJh&cS8ybaadUo&CT5N zW_Ky-RWAC6pOm#I#yr@pk+p7H6nMB0;7g#f>eg)&NCCnf=zP2lfnxjo8DD_9D36@e zK_Y|En|Y5&3tm;^g;BU|b8~8lg;&{$eguv`nyO83y&sVv1cJ_Y;1TN5W5?$JG@bzq zyO!pKqH{Z^S}9O((eJfisejd#c5JqL> zYXd<5n!7M09!}2m&u{+u>Mxo3zZJg!fU!uD`1dk_Gx7f~%$EO8tNK~JYM106+luz# z9M@4D<-eWqK8f=EJl|*9q8zJ|d9FcOgu|B~=cR4cRoHreq0RNd1@wG{ANhL_MSt)l z3dBvuVT{HJEBV$)DfqW8e>8Ec;{K0+3#k0dqyYv>km%;+gaQb<#zxJR_X8O$b&qfg zC_>vCZqQ{+lp0c&s6`rTNCQa}+^eq*whZ0%iGJLPJ=ch+RNDl2Z@|Z<5{p+|^>kb? zzq;b1J3vVsfRm(5Ii#^v3wDbBB?LTk29rHctCyFV36uXq`CE0brQ;vkD`Cj}M1Ljr z0le`b2D-l(zQ3)l*1~6e`=Wk7VRnLqMb&Bh!4+{(4=0HP-uGKms#=%Az?hvi)p`~D zbk+EjPucLbz|=kcz=84fp7D7#JnCPCT0K>m{q{ES6V&hE50c?_@>wny03ZkB zn22m%Qh3Y_%xTMugHXxCrqf{axRyf}k?CPN4C+@ce=~gy!{5#LX>Ch+yK`@Fyi4}G zJ2K?-pLa|niA?o9gXH_#8B{N7eSq}K^=PtzEqe-gotQrw3mq3Wi^v*>n%j}D3nvFT zfnshEx)F&xmhvo(#Uz$01}%eGMj$s8gOYOvA0Njps^ zwvnFtOvp)XWAFhH*)0L@hP5PV^Pm{vH+aGmwDh;1sJN85VR)+@gms@iXrcB`pA6fX zHElw{mT%%?B%J6^ehHg%?a`2Du{&1eJ6-#Xa1j%Mm~4FNi0r_sF<*IdpUz<4^ZTGW zxAIk_4(c#l3w6C^3G1BedwNbHT&iJuT-p=vEBitUpUxN+JVDD{Zp0PG{ZFqc{0NwL z{DEi^5IzVD{mfYq# zAEHjdM>qv5x1AV-lk|FIU;Uqz@5ch{)$d*^OqM`JH~Wz|_jj9^eE}!EO&T)_o;iTqWg|+UhyW_OQBD)^|LSPB470e}%~XmYij!-KbU%K_>%WII;f4 zLsoV_<v>n z+VVm|T8yhm;3A#Tl8-6(bvH2IU#Mq&EU|%@UaJ^jD zPA;GD3r>1G%l}|F4E#5SL!9foP zW}!0C^iPaS^y%ILn(oLPVU)EILwd)rR;g@Tv$)cOC5~Q`SeUH|7}; z(y&d4vj||wI58jHL*kCs^>yCHxE=RjtT$%^w?H#$hRr<%2il+2T_gF4N~8aHQj-C- z{@&Tla3)^a9|yHtFU0P#paaW^R6EDw{~jppwW*WDOJDfC*5$uoqt!KWI7M>82+d!k$_?~B=1o*%OM71rx|3wa%I z9A{Cq#KP4Edt#qcZo8`;K6aYX3)+1gRv~8gLSi;?!)2Fj^Me}gSD#Pk{`BiM+-n>7 z(|^0Z%bZJ2_1iX=P}Ou&Gkqr`o-62t65A4n-elpFu>52vU{^Z0Ml40ePL8co50SS> zv+`4~JCRBirXZpg=9Any7xnyG2+y%Ryy1ub!ozKh2tm_H(98=s)|3lvW8x3h7}f!5 z#O2A5gG_cOkwg}L{>n+BTJJ{a2NJRV-o@1<(nhx5VscT8G7qc;$YT-P=#=$`r2io# zd~EpZz~p6b-|J&WLPEe;yF4qKA#pS9nwK zS&~r7%OFZ;%ffjwM$Y~mQAeQrAa#Rpfw8m~yE8}QrL0$1U#EnHoz=XKn%}U{P@wMR zAciiVayaOglCZ|$jsG*9dQTRI&Ogd+4U$%Xxm7@9BY1CPQkw7Njh`srwN%wFxF#5 zt6_C@h>VfC{3PmT-W`#|0o0 zEk3s?jiR)KU<2s~1B)y|+B=DW<=i1{p;FbgfCI;8U{#;*;1@IALsBG*GXFtXm2F#n z96N;L!1~vp^3So{)N)UbN&2pdpMCyCYg4W&`+JVjW>^J2D}FlG9c zp6!m!*@B4;veEOx#OfNKb{{%;9c8#XA1cnNc7-bPdBJL|sx(-O15L<|xFYehxQ@PU z2DUA-dy`cnDu!RBy)is;NP<#1MnB!cX}I5Y(0?RWk?m{GzqHG)hZ5Mpoj<%f030XI z`4pJGml~mi%{hQ)cMu}erqBnB$wxgFStW&p^*1s5>kaw>PO*cy zc}1G>@}YYDx(`Cl-`2$461Yz#x^3GLrJwt)9N8U~uDj2%?w@P!ibtZ4N5u_AxHu(>|w2BCsyXtA0~vf8<86527*)Nvh@y@2$|ZXRfn> zZ=UnlH=y@>2nS+jrE@t`vd^3%H>u4 zBN~&M`{007b@{OaiPajn;m`Hc$h}#A0>>T8>JN-^tGV=#McTzbN6C;z?1@Qb*PgX4 zJZE2|O4E7y++zqz8^s~OO{x;_vKCqRO4R5T8Bx{8fje?~Oa1Q+L6Y=c3Be{W;GwFDuctT(IM-3Ttf&P#dCeWXu|CHM1%o;mUiXAu(t?eKDR*( zKQV(2qW*x>Xl7vvvy)=Cw4nHj;Qd?qKtOKhRQ+#ZElIZCU=eEgYtgibE(JWo!1S|# zc6rGCvH!EKkitJENacx3j&z-~xhRLW*;Vq4@Iyo&+>9spJ>&)dl0=VaymKSm^euEDI zrNgn6YbBa_r~(6yYQ3Q!NK3?8fw!1AKDW8dD&O+_(Gqh|cjV@ocMvKLYy~7DQaOS+ z*+sN=G4;vtxski)0tL6q&OqFl9q_p4U4CgL9LsR=DczZ3hK4*k&f=29-;WV%EG#U{ zW(zr}NU9|ji}s8d zkL%e6d7`%Dti&SWWQ%-dus0+&Ak}PXc6@p|LCl&}a8p!v^cx6y+ISh&_V!9s`DuNn zCv7xla5I&8;tugh1B1AzSwyqL40S*TWifB}g?!RJ`pKSRauJWpH_-e}My-^&j>t%T za*rA=81u3G8D{N7%~bfBr8Ls_pLdi?g;Z;LL#-sg3BV?$9?_35^!{{9X%Jw)#1T-} z=Q*Ah<`=6}1;Un3AazFK zPL3D@#<7unF2nP4m;?9_B_-QNr;(W?WbrfRqI)U@FCE-8IE##6|GsD~_IhA@hb7RvLJOlYGJ%N_S-M|D>*}5G*7Y;~fS$ zH&LMNv5DYOi+28A7A}XN1q0Ef!I6XuJK;g1%l3s^#(H#nn|` zUnpj^&x=L`2|q?_>z>C|>B#@+uY`J3$77*qBXPTcvUTqyk{L4}qKl>&GU<_Z-J|m-6saWjSCu=>00E z?U&H=c8<&KW&0B>lk2BF=6A;dFBqC7=Ad`tXQp+GPn3MRd@s=@&)&Nr=hRG zrC{>MO`X&LN47VJwU#^JB{@rE)RWw6UUpz8Hoj)Ul)SwmYiH(F1jPCZ7bPd+4>KPE z58fNdCOa#@r>ED!S?}ALcYL(rb3&ZI=689Whk5+L^kXq81hFs?HWyyhNaMmRs--)EB~d819CEUo3#CEoZ$WHYEF<&)2byzjMItqG|g zho;_Qa}D000IBzD?kbPf6q&Q$0Xou&YqIqS zeXER4_IJB_2+UYyB%gL3MZVl}4KR~UMA%pcm@#tVtPm8`?~H}_JiO`Tot6itfYm+K zs4{xJHdFKt*`%v{0-O$TAA4@OXyYmj5e&NnRap_+Lfc`UxTx?%IKQ}+& zv#&($RyFu%)Y^I9zA=k!TfeX67BEM^$&~q;ud`wBXU2BAOyPqZW18PL3?T;YGs?NS z8MNDgiYGo{-C|)?E}Sh4CaHVZ2`wwnV(5kU5VvRgdx{o+Sla_gMu%NR_MWKBdsc5H zSknaJ`D`^l*u*~j-rULbQuptYMadHcE!;@GC{at;`}>;Agj16KnkQ$Q4tZu zDqYoaVhfe{i#1op2VV6=NQw2}{p~`-(AXGOVY_^g-m!ndAzC{U@?#Y`M2w>oR#Zg9 z$FB;g84t#ZQF-T;NOfaZKHK2NTjPT{2!dXapfF6>@l7WCdT3H3p zm!HGSZY*gdtpt3h28#-$;IQI$0D-k%9!!iU!g#EzQg}2T6fH z1I2nY56VRC?BH;ibo??hN`0(9ko~GGIVp6<0>hChmlURB0w4cSA1*1*VdI92Tpuv) z21C4{;xJBXm6@5DRBFg25EH|8cTY?vBM|VSSzRrCMIH|df^oavM}BL^gGf8De`8!? zxaR#Fa|Dpu|D8Ln}GD#KUh&2NP1JB2w zA<{$ut@7wVA^JpS+s_lLOV197v}Bc-O7^Q+0+N(sFT z!>?bz`haH}z)v!e%p{^A6a&)XG*&jMwSQ>-is7OJ!`AO|bfg>hzXe)j<)x$5$|gZE z+azS?!L1l|VLK-$0hQXk*MUmf+S;O`q8F2$wf6Ju%sf2!6`G!xJLMc9Rp*pj=`(7< zR*=}?03-$Z;Kxk%%Vf?7qP7T^x3+%id^|$bKUrICJBY@lmeb!E80QAQevpxgm@ZZj z(XV7@V`C%Yy_;V_fG2B6$jkfie`IxvEn2Tj%8|(W`Gt=uls2o)v@(bR+D$6w&CHB{j&g~_ycKG_{7x*XWetFgjFOcjjWLYbxw(MMOa@?G zgtvj=PE1;w%EcG}&{z2D*Ts&nZ}j~<8HCm*<%8R83hbx8({^EF;(&xj&VQG|cKIM8 zIzrAoIi%W8P!6udqk&RqSeq<~gOt`b{>yytKE&?0d^!hSaG*_Q1#aFRH)UTN@NeT2 z5Fi-ZN<$nTe@2MlrO8@Gx{)EK5_v?}JEsPlY|D>Xs1!9ag7#yT;+IXCC5v=K2hd|6 z!u;aSrWZ&~o_F!5$bWNpH$*&=fQ*F&-5%UzY6z3A)VPdMMW+!`Q3FZ&mQg>IQRd|v zUYvJ5U5s-nRy!Wp2&l0_CfIn82rN)lKCZ_857R?aZ7o)iS^`!xlB)^k~K&7`B>a|CW@Jx_tNe&X*uZSu?caeVEp;&Ns& z3B|5()1PdSiX$@ZjjI#ev0Yg08>EI4AwPl1zF(^~9EjLP7~g<*l++flX*8J&bW=7A zRO9E@)-N{VvRxnW`zUC*KXz_^Q;ez`|7pOZBc1CUR;$e=^L$Yha>f*Nb#?IoS+NSI zooY%bm`IxlDl0+XhaDWQs}0{|Uv*jLkE`f6D&VoerCC17OKR7HQW0K7-KKtWYs&$--tivO5f(sc3sMG01%Q2DViH$1gc^3i58NiMR4h*EAqAJwmI2qmmLKmHZEDBd&a;^tWj^qw^8Rya!dsI7uCKBF#B(wpGARN_{ zg<1BWuWir*qdpf_c;T9+XKMnO~27171@#F1al&_#F40o?oBRFi5NPCZDtU+%@pZ2tCG@ z{5f{g5Dl-oW5V;(rr4(Zlb8!)BHZ(8+p#Szlb7ve%MhF?|DL|aUzKyojM~1p$6TBJ z;Z3AxuJ-l=d18oqhKBx>(n+{aAApcJKV($>zVR3!!1*t#4sF-jg%b?PngA@%nBrVS z*(kHmF7wzrwKg0r?kI{~Q-oM|0k{Ef6nnXzBd4;EJOcaNP8}s>EXTmNr`nuQTVtO^ zfJfN+9Ix92^W7Y-W|`%3kpG?wEIHm4d5Hp2BMDrqC>fV z{7kj!xSC>zYu*Nlu9qN+HGM)Xp*Q>+zhggI{48Flogi2ITElr5RyNwAtL1x}(#XZsX1Z^6@D$3++O5uo7X2-Wj*5mQ_s zrBv_P9Wv$yr+jNfVEnw)!XZ8w#8oFK#)hfJ-)*RIO%f#W87-!-6%njz8sx&6hx4WPzT>!#m&i zSheA=);0@YCO{|XHk+;t4?GjkJg9AFbzS3U1aiSIw@WEC(pudoQReBvLi$f`^t{=_ zFQ;26JOzX&w=G?fTE7Cb23sG}?Xovqr@`Y2c+HfXBpJovVCbh z)`%8lByJNcwbj5lRe!dJ^gX9%GL4}>@;<8IoHm3wTWwGlZj2VyUSE!Od=Okr#{~;pj z@T;_%e5bvgab(F!nOHlCdm65`8Dt$vB5`}`L}0)%`jxE>YjN_0q@&C zqENij=IqboDfg7A$o@htqXB{L%KmVBoyQu!DB_^kS~wC^pFY=>egIlQ?EM;8|EY$H zKEW}(&*?unlYNcEqAnbo<5yotEPDpv>Xf|xc_pfa< zIJv?EfyC~jd8wgHyF)&no{ASz&(OTv!o<#7JR$WCKqKpe7WY}F z6r8f46*>puDUx2Go8ArI(`GKvDQT7OC3sAXbjl^Ttu`+<>|cA8?-(c%jG8utgN~Sz zw^Wj|tH(!4DV79q`ewGTHH-)9Sg#BU4qnRSt;1DM)@D*u|HTiEW);yyMaqiEq+ERD zw9%S0UN*VN#s(x^;q%N(Q+qaue#7S(SYC!)YjvrSHq&wbps25eiSM2{-eBo@3IkDB ze+TdwEZezw1ZLKm8ouhH=FDv?M&P`m+yv`w2kZ|Cf(esLG9nWXuC{o#EN_xM4!@Ez zM&?~6UwAv8qvIWsjnVm^KQ29cn35ns3Y;B4Q|Cdwl($GKcBAyGk66oVgFDwv^A)~P zI$A*&v80T+4DjY{APP&=eV;eB*VgoC6N-Va?Ur=>o!!urUKyIhjr#B?RGgsANmxg9 zcKAk@DZyJnXZ8a??lWiK&&eRTNlo^_Vp{<}^&uO2qm`~Rf%=um?u+?n8T9|SH>(?W zs$UU9yCQ>jF4piQev#<*zMWRc*H68aRef4+;B6*~ftEI{0-5q^v^tQnLp+$e9E<;( zd9j`R5A%{~wnQG`Dce1GD^l}@TKR62ithnyhy+affX zX7a^K^;s6qvNtD2TyD4~%gODe;RCp?=0p^O02xmk?bUExlduRe4SCva}KJkm`4<&)_#mSBsS9`)n zgw#JXRb18=!$V2m+d970kA+>k?NQmUA|%*#(C&b#u6Zh8oH?oA)}f7e+N=8H}LbUpEr8$~+b_7rJ=`ntx;o z9z738(Ys)`_mSYF99;f-qD?Qga9oAkVH0u?diA9zSakGq^s)9K+Mf0k&c4lQa|rc? z&3jXbA5`*zzGzVmFv!j!RQ`YxynAz;9SFgYGEBneI<+w?= zjLh>;)W>O=-hHm`mquS+URuUPXbIS9c-g>b=VX>q`*L641g;6a{Qb~(p9m`@Ea9&E z9MIg1h;2k`AJXl~WU!N5Z1U7A2zogm81GIyf+TG^-`2e^TAmQ-kVWgne%K#$tK462 z2>Sp0OpG_OSYs}3z}&Sybkn5U`<^jjQH^S6z~&ONpq8pf%+0}cl@TOGU2irPSav+P zUO7JM;}_qy)cDj3jt+-YX$Z)-qZT;0*NS-d!nu2_v%VPI5^;^ST7p|GMc{7>i0ph_ zeYRZmro;+^89+N-Cbmjrhu)~X6+&BiB_wRNo`ncETg@!18v?a^?T5Cu9e&KpY>T1m zMJVizLB?0aC;QQ5)z+F3SF3g1s^a3*QF>0Bm{p_1O5SQQJBnOpndrBXbD_#E?+(^2)(84;;?pT-Qs~Nw1KID~7^otjHPl?0ZLIbUqG51x`)fjR|4KmHfiRg~+Rc+{GI`ALqZx+e&rmZ;m5bZtB_4w+9Ux%~lQ~ zh<$%AgljQLH?Jq${=Gv&!pa171_q^_ZJ8@gFK8r;?1QCB>t+V&VJlCf<`gqPfXkB| z%QvyBeW&4t&KVzF9{b^;sTp4ZRhk(fb|f4J#au9@Ye}>O7VyDE9P0Y>>LM65d*5G< z$iHjOdx&)=C>{AoLTk{)US`Pwos3}K4riuDX<6@&%acL|>SSQ~RvazQyb1Z(^%%TMlz@;Ut>b#I8Zs-pbIK8si6R6t?C5=Z?cN8tRv0 z|5mcNQQJ^(h1vCX=(VVgMxIooTQRC2euSnHcAHcx+@4%>{lbt8CJcJC3}XX;_8V1@S7`VxF_BtBjC2f$hKbbuBjT@5&kCRX z3W;ygdGhc~Yxh`t8MeD=uIZ5@UAZ z_(uB8YNQ4nVNxxOM#^^wwjDp6-x3!u1~uqYSP*wwIRd4kZZbuR^jBS89$AQVbb6u; z_sl+Qdm_J=Vz{^+fPlBd!ejRvH0)dloC>Ui1Lz-bA-gjszYE^hKX-V`sb3RLvtWC# zpK0Z_Lgsh89(TFSpCdV}ms(7)#$=?<{(z_cJb}KS{=nVNGI|_`j|8?2yQ_$A)z{M- z;eLAy>@sA8*VPEt6j5o5k3)J&=+mU@_B@PRaA^Hc>@;T1Q`9Y%(=5ciuQ z9jV^oU&u+$5a`IVU9p%D{Ore$6TbN*1czHCAJm+hh|h#%KS+7afhCRPCDzFBfb^VA z2&$nR?KjU+jt`Oqo~Takg8w{thSZ(~CKA4a!afRauh^6JC{QYvGQ;;aru6pj;IIGH zl|=N}_E|GQd+)^Dd^)g!aq^qzy7QY>?jU33{^e@NWB$9-;II7^BPj46E31i0JHYsQwFMn|5+DX2J6%!hBzzYKJ`Yi*?|3n== z1+V7FX9lQeY>jITHXN1^V5&@Z~ar?4RX%kkMkDEcps&kTBq?U%Z{FL6f=U#pVD zEHI_w|636$iEb?!XKq7QC|+=@&dt{Ryt?>oL-TS%8=mgcCfj#?#hWrryX6H*)g}AM?G*>SeVIm zl|I>R70&DZHI=n~=$&27a9_SA#vr>D=Dy4`NttT~C@{y#jO#3TNiYgvg)sDaZQwCg z&=5F9*z9zVHvrnLHQt`N`j!HDEIzwFlIR$v=fCW>q?|C|^8$Q2O016z#;(9s%S3mO zh47dJCHJm7SH&nxU55{G4Uu}f8_-qx6Q9T@&{Rk@KwF;;o(l%QPcY#O#*wW9A0iZ0>)BBUk+Zk`TTi;0nkLQv4wY;2f}xKEqAqyc z=kn8R#1%yb{zc&Z_#3#q{S2soY#!`*!Tbutq-+b zif!De_slh!=b^`7^Fvb8EHL35N>u$zcOBF*>?dfj4Lln+ok!$%c^>pvT=3@59i$BA z5+#nIqJZ?h5Do^#OARocNk`4K8!gp#)Ba%Oe}|Q5&{aoU*AouFRPJ&N5(evOCC_XD z6>nhbedPKb)`|QYz5YH2el9f~X9g39Bv0`v$HL|(q}jm7gGt#wJVU zylFEWi}-dp20KgCnfF6G|AOa7lIhR{#`2R2l){+#Kb(`c%m!V-;PMEc>C8iSN*@+p zOWRil&JV+}@T=b}Kd|*}xnELONa{!jISz=?SR-HZAd1hdP*()B1IKq8cKLO%TjrFj zpkPtpMfL_g&YtggqA#u|5%0k%CB?-#vgtFx-SVXoU#Q@~8dvlXQ}m^3OD=?{G%W&_ zH!PjFz}Q+X&j8a$61*WwDFA@C8I>R;i$r4xz2i+&+y##hh@4cR6AJ%B&`t%fZfC)= z;2)wX&k#a14Hjj*eDpZ0&7xS>fEGX!u>dlUPjl&$Swfe*o$V=ZmnxNx6pHpyO43<$Ww;d*< z-LTo4kK*$+o%u~~^V*fii~uKNc(;GnauV(fY^NiQ59+-#Z5XDsnYD^+6q@~97>WrY zmb1>($HV#QXxoqsI0mcS@luwne0^ZN zCRnY?VNRvfa5aJQMPjXg_Sd7?THAZ2@Hw}%yR-;OM5y*PLb8cZ{`LV2JMCuz`Cn;6f9~Be`_W{M|XM!a_q_%upSiYfIa-bv;9j)rI z>hA0^7fj~q>S{>HUTR8y)AOAJa{pfbuci>B34!t+1_|G+Sh#Oqz5bp6f`~_}5U)hz za@UNZ_&f6LIfW`60gU_>J65W-ADG>AmTvxZFZ)R)^~3-j`bGGsAQ;Not$O$ zYEL!?<>g+Rvmv6zzsrK3MH#_hNbC?trCLfhpjhBX!h*y71@Bg~>{n0AVFo5ZR*jk! z1RLwGar-wkCa+QA{+2duIqcb9IxTDk*>iz37NLkxMv_)K;Wq)k+)|&M7LYzO9_a#M%k7MnCs3xZ$;8i-lX;Iv!5J*{KlA^lNC?c}D-Z={HI0X2->4l@+=iQz8$w$C@00&>j#q#ys z!7A{R{n+Q3+l4ZJvTrA{#`^gw7%2eS*ZsDh1|mp%ubjL(m>TV|VSi>=?Jb|w1JarX zhjhyEJ=GV|F&At3-<6)K$?QW;+drsPjVh6+bk{nB00o?8>>E??o+IP-d_c!e9^Dud zl41S9#@141`n%MU1@Prd8Zg+>7?LRgwK@Y0RvL-D4p@}|B(MuMAY?IH8U3y<+V$`4 zHSm_GNeKhtG!!PH0|#F(Kx#w!NU1RF5?-Sg={Bn7RH#&0*t@=xM z)lV>Lw0Yx@{MU}y{fKn-=+q5{^-dhql#ZHkg6Y*?;ta%RpN5OcK?~8;e)1{)`U0s8 zev110+0`@p;LxCqM%okQ$Zb&Du=ovgf_Bt0)bR4d*joy36e{JcsrseN7o17rY)%_pWuN=t>aR-oH-}6vx!w&n2OkTpv1AJ_;6_r_}|88q*X=yccQ@u=)w0#>i zke8ICdAo;pM;)GPjX;YHoBykk(f6)9a~qE|RX13FmT~9$o9QW4wrqyFRsrilq^i2o zvA^L7NV?&OrVm3{(zw`+Oyys_!|5@xv6XlC65sfPYlkK#f~wBNXlQABrl*BcG<;4N zH(y_ZFBWux6kJUfjfs%vvwHSbwds3d3je%XX@oiiy-H0;OlrE)p$r%b z|Dp-`5tAm4FDx9w_perI4o_jk|7p4{s`B^G{?%lhoXbM_yzi=)WF6|MyLTQRMUKR- zhC@isid(0XQFZjhVJcW;@V%K~TmfXan4 z2?CYf*&=Gi+S=yHDm2wYY=|U%ev3z}^`~E@3i8?Hk?n&y@e+oMJk34*{qR9N(;<*y zUGjY}7q_*EMPxbtKoYa?H&)|$r4;xg19(8z22&sKW3vvSt6F!hl)u&KCa5l+H>08< zmWbH*hLy2mX638oFoX` zBJ%6nMBMItiO}TIH>AR>S;ZE5lCfhNl)CrZDkqUB3fGuu$aR7=4lFL_NnMz>h!8H+ z9lOTu;KmyTTh`l=im4RHlgNZGbl|#mQ30plem3QL{4U)|mq?%<5jJWg(9hA6eICOE zL)aqo{vG2PvHl+pG>bE9JymC4RCA$0yZZ}DQN$hRAU}Q$pPa~;c;4p4Cd^LwB#1@u z33C0F(#k(+8M$*XX0AzE2H-H7fFkJ7yi-RpNqXYbI(Jn5U`-br^O^-c2a5!|Zsyq1 zAGy2t{78|Ol+A^_R~BI?Q>b4Eu3L`RNYg{&$Sm^tIXWY2OfI-` zPol++5leTP)WFM>3;vXt-M6c(3TvJ+y8u`IKS-;3HQBEKwPVge&+8;J1KWu5gIv8b z!-n)qZ9#EDRoumtqn_qaHl-|;_JOZqt=K2WQ0T%8#jUA`M-&MgjF3Jx{ygPtnz`oj zg~e6RA9C&S^Uf2$ijk4RGYQ8|oN4USbx+OXgee%WGVC@9k$&kXuEMZ%37^YgKML}# z41@E^`b^-$7Ob(udEec@L&WfNaZ1hS4GT##&k{Ug*Wl`_XsDX9FF1JpYrzV6mtVMn z0MhM?FDIRD4tjrIq$(Cl$w1A6fyX-$2UjX0wA$A+`^;>A*O#i1z>RGBC~x13&OS)Xcsy0 zja5mNY3Yzl6Tzm=Z2OCTu`Dh@4L8}BEcu0F66enL71QJ{vKfjj z>Nn*svGlJIv6SSH$Os84#1HDr%(eyrs&P;1NIry4w_%Yll#QZW(W;jfFZcR{P$?}9 zfw0M{b-(4r-JB3`li8<=Z$CL%ZNYfLy0SOU3p*Hn)=}t=BZSCrhRtIlP=I37U%5Bi zX(98+)Z9Ksuqaxg0lAilbC`u9+W7FQE;|>#z)R)@{IffYyg%r;cDqA1T1d1ur(fKN zA4#K#mZJbQT0OrL0p9lY1WiHK1Ijj!HbmgbfW|L^ElQxb*6a9`Tez6zzNpqC;j zG=nV6QFO(EPGPH=@{_0-_E`)9+L91%GxUbN1xxFTX;eU$22VE0_8$lPING#&lnT?x zGrdGJjE;Fj(2Tj*aco#LczJi6yXA>OofVbYLl7MAE+8Fn@>Ys?8_Zh8Y}9B;H=jBG{nxcy_qx2uM}YgCF5vbQrWcW=y%J#ZdpG`RC{?G~azWYN9eN`hgKb!})&>$1_XI=y;^{Ty|= z*{DxSLi_*DrwRics-=DkA1zBP`Spxfbgz~a&|Oy^ID*`FEe&bo{YcN>p7ZidgW~Y# zM5dhp>As|Z#ZPfi=y0}2=&4#;K&lvacSN*ySIP!_4a=Bdehd3ir3d4Jyhd2e!aa&wcL zlic$iILxnQB639#DrbRNHjh7&6y&ji%QPfI=1|t*Lqu7^3Ukw z757yY#pZQh$NB~AX+_^8++zOg%)W!3fOgu7v9+`!!bdx3?4@%sRtF#wHmO(nqEERo zO*vPu7c9qOW7Out1!~BO!4Zxc*uQbR>ek=kz86N)RCUK~Ci{@<$}<^0>Z%D~i2tddh6oc|wquiGf?7L2hawza!3|PK zP8vG|fe1EXKa{QOFiAQK8NJyv!m50k_Kts}r`HtyF%zzux1+JG)uh)ZY9iV`%;dM- z@UBIB9I4Y2>vo==6wElr0lu-;?)Ec-VS>taq=Vn<|E%3gNBW6iCm0xORr-D z_)%0ws5Ny3&lshtDgDHJetcr|U`rP9vds%*{bV@7)T8c#&rc?KONF$fP`NX%fu>q5 zsI$}%UdgV3;LSyTWYQ^ZiWQn$L@Ea+VRdXOJp$pF`sokGxygC+3g?by=AKxale1U$L>Cq+jU^L6(D%s-v^$Xto5ZwSf_^6C zoC;b_|4fMrI0p%vo~C9S7-Gr-j;lq?j&H4B(Vb$=P$>n0*c6DelhY9P{(qWLhiiXC z;r}SuS3bFf*Fc3O2<+A2>(__l&5UHhsgK;8eb9nSx;}DpE}n^T$1e;jQNck|Qag1f z(?_@U%h#OaL*{Yi7a7Y}lIFKhPrVdWe(d0WE_a5RPDa`nm;kgtO( zj)P3H(yXBx-o0k!TFku_9wXxoeID;O3)LR)faEp;O4E~x!O4X+QUz-|FV4}(#?QT! z7WCBT+UbrAU#p@(KW~?ZTexa%>5i?Ck3wkVy#+wk$S;St|2OC<4P3pjN7mx~15~_+ zB}zBvgSFm^@-pTsU1m`~ME-{cb=H*pzdBD`ETK4Va{6;e^OxxgubcCc^n?U*B|L?P zBHw^6BZ`%d9Rx;F5X+2L?p)6LpGH&v4n0|o{2l0N59x%{7Qu=`_yCoY|Hs81<2KNH zkQV86`5M+87ft~%#4Ys8yOx#pYnS(-y85nU+`p~g!W#pj>e<#md4;SrO( zvaRZkx}lh&V)>L*q_+6J^?r8WMTN9nWnizzvcO7qJ{C!i;}G?PQ%AQyvyg~YyrDyn z6qXaE%~n8~jhiP;4k!}aL9TBu0aymBHk(~QnIMstJ40Zlrnhn&)OTceZ!Erg;icN) z8oZ@w7CyFHI29OH7AO__Cd-<8%ik$ZE9LY=U*%4Pw=D+R$%H55Uq406KIuC~6U&au zsH`uLjBU)7_x80eoZt>O`g@z&i*jQ<8^B&IM!b$JHKmhPO_73Hw7EBn?`$VuU2Ats zM^9_-t>r=e*sN6r<=Iilm;m8Wrhlipz4UTGqt=ENi zL~>GrQTs~d@;>a_%_IZXZ-IS`HuiQ9Y&+dPlq>W>M9vu`e4 zANA5O7$uYhhI{6n&Y8<`XtV8~_kPo0iT%*U!8g*3Z>vZcv2m-u^I8PR&l*ODbcUJ} z=M+`TIwhwm^A4fHu&qi(hTD$CQ2p=*LFF3r?04C`qE8b57I29I9gJVoOxqyt)A-AW z*AaH=%K}L9k=`96fA2WpBELlWlRrYe);r>LlIey>D@Vk}wTgdh73> zWn*|ii^{?BCFOxTI)jUqIg&)Qmt%iEHopacIboLNRP?1zpnfrECg@u$UR{m+`lk-;+l zX>m*!q>OM{Pc?0qNNYc5RGs}TJZnB zchnPlp>JRyi$W>3-2Yu@CT-p6O{Yg=NbTFMmzK&$N1I7<<*$o0O~0zB=-zzOWCI(M zch;*p1CscrItbg7#SIvJ^jAb zWyNEtXZY}Lxd6HBrDTqe5M}OPFaxB|Y)V>Ue@j%Iot-nqVMwnZAhQ*%kr@}hPNNjS zw9{>L>FfZZ=Sic(IGRMI`WoKl*X_TX>>MmF`#wQ~LTd89=e;Pr^#911+9S28XWJA~ UUy7@;+aei1V-BJlP~qqQ0m%UBy#N3J literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 56b35a5..90ec6c6 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,31 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.4.14 // indirect github.com/PuerkitoBio/goquery v1.10.0 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.3.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mmcdole/goxpp v1.1.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index be33712..14a8210 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= @@ -5,21 +7,53 @@ github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4= github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE= github.com/mmcdole/goxpp v1.1.1 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8= github.com/mmcdole/goxpp v1.1.1/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -30,12 +64,29 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -43,15 +94,23 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -65,9 +124,14 @@ golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 3ac7ee0..1207cfa 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -1545,6 +1545,37 @@ details[open] .summary::after { background: linear-gradient(0deg, var(--color-widget-background) 10%, transparent); } +.docker-container-icon { + display: block; + opacity: 0.8; + filter: grayscale(0.4); + object-fit: contain; + aspect-ratio: 1 / 1; + width: 3.2rem; + position: relative; + top: -0.1rem; + transition: filter 0.3s, opacity 0.3s; +} + +.docker-container-icon.simple-icon { + opacity: 0.7; +} + +.docker-container:hover .docker-container-icon { + opacity: 1; +} + +.docker-container:hover .docker-container-icon:not(.simple-icon) { + filter: grayscale(0); +} + +.docker-container-status-icon { + flex-shrink: 0; + margin-left: auto; + width: 2rem; + height: 2rem; +} + @media (max-width: 1190px) { .header-container { display: none; diff --git a/internal/assets/templates.go b/internal/assets/templates.go index 4834078..3512f0c 100644 --- a/internal/assets/templates.go +++ b/internal/assets/templates.go @@ -42,6 +42,7 @@ var ( DNSStatsTemplate = compileTemplate("dns-stats.html", "widget-base.html") SplitColumnTemplate = compileTemplate("split-column.html", "widget-base.html") CustomAPITemplate = compileTemplate("custom-api.html", "widget-base.html") + DockerTemplate = compileTemplate("docker.html", "widget-base.html") ) var GlobalTemplateFunctions = template.FuncMap{ diff --git a/internal/assets/templates/docker.html b/internal/assets/templates/docker.html new file mode 100644 index 0000000..e623712 --- /dev/null +++ b/internal/assets/templates/docker.html @@ -0,0 +1,44 @@ +{{ template "widget-base.html" . }} + +{{ define "widget-content" }} +
+ {{ range .Containers }} +
+ {{ template "container" . }} +
+ {{ end }} +
+{{ end }} + +{{ define "container" }} +{{ if .Icon.URL }} + +{{ end }} +
+ {{ .Title }} +
{{ .Image }}
+
    +
  • {{ .StatusShort }}
  • +
  • {{ .StatusFull }}
  • +
+
+{{ if eq .StatusStyle "success" }} +
+ + + +
+{{ else if eq .StatusStyle "warning" }} +
+ + + +
+{{ else }} +
+ + + +
+{{ end }} +{{ end }} diff --git a/internal/feed/docker.go b/internal/feed/docker.go new file mode 100644 index 0000000..81b1d12 --- /dev/null +++ b/internal/feed/docker.go @@ -0,0 +1,69 @@ +package feed + +import ( + "context" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "strings" +) + +const ( + dockerAPIVersion = "1.24" + dockerGlanceEnable = "glance.enable" + dockerGlanceTitle = "glance.title" + dockerGlanceUrl = "glance.url" + dockerGlanceIconUrl = "glance.iconUrl" +) + +type DockerContainer struct { + Id string + Image string + Title string + URL string + IconURL string + Status string + State string +} + +func FetchDockerContainers(ctx context.Context) ([]DockerContainer, error) { + apiClient, err := client.NewClientWithOpts(client.WithVersion(dockerAPIVersion), client.FromEnv) + if err != nil { + return nil, err + } + defer apiClient.Close() + + containers, err := apiClient.ContainerList(ctx, container.ListOptions{}) + if err != nil { + return nil, err + } + + var results []DockerContainer + + for _, c := range containers { + isGlanceEnabled := getLabelValue(c.Labels, dockerGlanceEnable, "true") + + if isGlanceEnabled != "true" { + continue + } + + results = append(results, DockerContainer{ + Id: c.ID, + Image: c.Image, + Title: getLabelValue(c.Labels, dockerGlanceTitle, strings.Join(c.Names, "")), + URL: getLabelValue(c.Labels, dockerGlanceUrl, ""), + IconURL: getLabelValue(c.Labels, dockerGlanceIconUrl, "si:docker"), + Status: c.Status, + State: c.State, + }) + } + + return results, nil +} + +// getLabelValue get string value associated to a label. +func getLabelValue(labels map[string]string, labelName, defaultValue string) string { + if value, ok := labels[labelName]; ok && len(value) > 0 { + return value + } + return defaultValue +} diff --git a/internal/widget/docker.go b/internal/widget/docker.go new file mode 100644 index 0000000..0ab93a2 --- /dev/null +++ b/internal/widget/docker.go @@ -0,0 +1,78 @@ +package widget + +import ( + "context" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "html/template" + "time" + + "github.com/glanceapp/glance/internal/assets" + "github.com/glanceapp/glance/internal/feed" +) + +type containerData struct { + Id string + Image string + URL string + Title string + Icon CustomIcon + StatusShort string + StatusFull string + StatusStyle string +} + +type Docker struct { + widgetBase `yaml:",inline"` + Containers []containerData `yaml:"-"` +} + +func (widget *Docker) Initialize() error { + widget.withTitle("Docker").withCacheDuration(1 * time.Minute) + return nil +} + +func (widget *Docker) Update(ctx context.Context) { + containers, err := feed.FetchDockerContainers(ctx) + + if !widget.canContinueUpdateAfterHandlingErr(err) { + return + } + + var items []containerData + for _, container := range containers { + var item containerData + item.Id = container.Id + item.Image = container.Image + item.URL = container.URL + item.Title = container.Title + + _ = item.Icon.FromURL(container.IconURL) + + switch container.State { + case "paused": + case "starting": + case "unhealthy": + item.StatusStyle = "warning" + break + case "stopped": + case "dead": + case "exited": + item.StatusStyle = "error" + break + default: + item.StatusStyle = "success" + } + + item.StatusFull = container.Status + item.StatusShort = cases.Title(language.English, cases.Compact).String(container.State) + + items = append(items, item) + } + + widget.Containers = items +} + +func (widget *Docker) Render() template.HTML { + return widget.render(widget, assets.DockerTemplate) +} diff --git a/internal/widget/fields.go b/internal/widget/fields.go index 2b60b27..47072bb 100644 --- a/internal/widget/fields.go +++ b/internal/widget/fields.go @@ -185,15 +185,10 @@ type CustomIcon struct { // invert the color based on the theme being light or dark } -func (i *CustomIcon) UnmarshalYAML(node *yaml.Node) error { - var value string - if err := node.Decode(&value); err != nil { - return err - } - - prefix, icon, found := strings.Cut(value, ":") +func (i *CustomIcon) FromURL(url string) error { + prefix, icon, found := strings.Cut(url, ":") if !found { - i.URL = value + i.URL = url return nil } @@ -218,8 +213,16 @@ func (i *CustomIcon) UnmarshalYAML(node *yaml.Node) error { i.URL = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/" + ext + "/" + basename + "." + ext default: - i.URL = value + i.URL = url } return nil } + +func (i *CustomIcon) UnmarshalYAML(node *yaml.Node) error { + var value string + if err := node.Decode(&value); err != nil { + return err + } + return i.FromURL(value) +} diff --git a/internal/widget/widget.go b/internal/widget/widget.go index c6c51a7..9758699 100644 --- a/internal/widget/widget.go +++ b/internal/widget/widget.go @@ -71,6 +71,8 @@ func New(widgetType string) (Widget, error) { widget = &SplitColumn{} case "custom-api": widget = &CustomApi{} + case "docker": + widget = &Docker{} default: return nil, fmt.Errorf("unknown widget type: %s", widgetType) }