From 1dba4e0aa4a8ae981023228361aabd56e382f397 Mon Sep 17 00:00:00 2001 From: Resham Joshi <65915470+iamtoruk@users.noreply.github.com> Date: Sat, 20 Jun 2026 19:02:03 +0200 Subject: [PATCH] feat(web): in-dashboard device discovery, share-from-browser, redesign + hardening (#534) * feat(web): device discovery + pairing endpoints for the dashboard Add /api/identity, /api/devices/scan (mDNS browse with confirm code and paired status), and /api/devices/pair (approve-style pairing via linkRemote). Backs the Search local devices button so pairing can happen from the browser instead of the CLI. * feat(web): two-panel dashboard with eywa-inspired warm theme Redesign the dashboard into a left sidebar (device switcher + Search local devices) and a right content panel, restyled to a warm-paper, forest-green archival theme (serif display numbers, ink text, hairline borders) in place of the dark midnight theme. The Search local devices modal discovers nearby devices over mDNS and pairs in one click (approve on the other device), backed by /api/devices/scan and /api/devices/pair. * feat(web): use the CodeBurn flame logo in the dashboard header Replace the placeholder triangle with the flame mark (the repo's codeburn-symbolic vector), tinted the theme's forest green, and set the same flame as the favicon. * feat(web): color the All-devices chart by device, add task-level combined views In the All devices view the daily chart now stacks by device (one color per device) instead of by model, and the combined panels add a By task breakdown plus in/out token detail. Devices that cannot be reached are hidden entirely rather than shown as error rows. * feat(web): use the flame image as the dashboard logo and favicon Replace the inline vector flame with the brand flame PNG (public/codeburn-flame.png) in the header and as the favicon. * fix(web): trim flame logo padding and tighten brand spacing Crop the transparent border off the flame PNG (it was 184px of padding each side) and reduce the header gap so the mark sits close to the wordmark. * feat(web): cost/tokens toggle, cache read/write, full feature panels Add a Cost/Tokens unit toggle that switches the hero number and the chart between dollars and tokens. Surface cache write/read token totals as metric cards. Add per-device panels for subagents, skills, MCP servers, and a savings / retry-tax / routing-waste summary. The combined view gains cache totals and the unit toggle too. * feat(web): use CodeBurn website logos and add community links Use the website's navbar flame (logo.png) with the Code+Burn wordmark in the header, and the three-flame app icon (icon.png) as the favicon. Add Website, Discord, and X links to the sidebar footer. * fix(web): use the single-flame logo for the favicon too The three-flame icon was wrong for the tab; use the same single flame as the navbar everywhere and drop the unused three-flame asset. * chore(web): set dashboard title to CodeBurn - Local Dashboard * harden sharing + dashboard for public launch Security: - pairing: cap PIN attempts (close window after 5 wrong guesses) so a 6-digit PIN cannot be brute-forced within the TTL on a 0.0.0.0 listener. - web dashboard: reject non-loopback Host (defeats DNS rebinding that could read unsanitized local usage) and cross-origin requests (CSRF); require application/json on the pair endpoint. - store tokens with 0600 perms (was world-readable), 0700 dir. Robustness: - client: per-request timeout so a hung/asleep peer cannot hang a pull; pullDevices fetches remotes concurrently and isolates failures. - dashboard: normalize peer payloads at the boundary and add an error boundary so a peer on a different version cannot white-screen the SPA; finite-guard fmtNum/compactUsd. Tests: PIN attempt-cap test added (1269 pass). * feat(web): share this device from the dashboard, with browser approval Add a Share this device toggle to the dashboard sidebar. It runs the secure share server in-process (mTLS + mDNS advertise) so no terminal is needed, with a Keep sharing always option (persisted; otherwise it stops after idle). Incoming approve-style pairings are queued and surfaced in the browser as an Approve/Deny prompt with the matching code, instead of a terminal prompt. The shared payload is sanitized; start degrades gracefully if the port is already held by a CLI share. * fix(sharing): keep a paired device from dropping on a transient pull - client: 8s -> 15s timeout and a fresh socket per request (agent:false) so the pinned-fingerprint check always reads this connection's cert. - share server: wrap request handling so a getUsage error returns a fast 500 instead of hanging the caller (which times out and drops the device). - dashboard: re-pull paired devices every 20s so a brief drop self-heals instead of staying gone until you change tabs. * fix(sharing): re-sanitize remote payloads on receipt A peer might run an older build that does not strip its own project names/sessions. Sanitize every remote payload when we receive it too, so project names never cross into our dashboard regardless of the sender's version. Aggregate numbers (cost, tokens, models, tools, daily) are kept. * harden dashboard sharing: version-skew, lifecycle, server errors - normalize remote daily-history entries so a peer on an older build (daily rows missing topModels) can no longer crash the chart / brick the page. - ShareController: commit always/sharing state only after listen() binds, so a port-in-use start no longer reports sharing when it is not. - attach durable error/tlsClientError handlers to both HTTPS servers so a malformed peer connection cannot crash the process. - reset view/provider selection when the viewed device disappears or lacks the selected provider (no more empty/no-selection state on sleep-wake). - by-device chart: stable per-device color/key so bars do not reshuffle when a device drops or returns between polls. * polish: device identity, approval guard, queue cap, formatting - give each device a stable unique id (cert fingerprint for remotes, 'local' for this device); key the sidebar, selection, and by-device chart by id so two devices sharing a hostname no longer collide. - approval prompt: drop a request from the UI the moment it is answered so it cannot be double-clicked. - share controller: cap concurrent pending approvals and allow only one per device, so a LAN peer cannot flood the prompt. - usd(): render negatives as -$5.00, consistent with compactUsd. --- dash/index.html | 5 +- dash/public/codeburn-logo.png | Bin 0 -> 75672 bytes dash/src/App.tsx | 523 +++++++++++++++++----- dash/src/components/DeviceSearchModal.tsx | 123 +++++ dash/src/components/UsageChart.tsx | 163 ++++--- dash/src/index.css | 88 ++-- dash/src/lib/api.ts | 116 ++++- dash/src/lib/utils.ts | 25 +- dash/src/main.tsx | 37 +- src/sharing/client.ts | 9 +- src/sharing/host.ts | 32 +- src/sharing/pairing.ts | 13 +- src/sharing/sanitize.ts | 10 +- src/sharing/share-controller.ts | 151 +++++++ src/sharing/share-server.ts | 20 + src/sharing/store.ts | 20 +- src/web-dashboard.ts | 139 +++++- tests/sharing/pairing.test.ts | 8 + 18 files changed, 1231 insertions(+), 251 deletions(-) create mode 100644 dash/public/codeburn-logo.png create mode 100644 dash/src/components/DeviceSearchModal.tsx create mode 100644 src/sharing/share-controller.ts diff --git a/dash/index.html b/dash/index.html index 017df76..978f7b9 100644 --- a/dash/index.html +++ b/dash/index.html @@ -1,9 +1,10 @@ - +
-f0os$XmD=9wixp?*7|v z`r5|C84bjBiKu6I1wQD#6p*H=5|A?wkY6w2^A` }0pDolcVs)ffEpCz}^M z;!iO&) C>A~~O9co~%pav{%n z rNu*esDiJ$oV)cdFp(Rt5}(daXWr{E{{&Ubh;7m3zci zTW~(>|H5mB(^mZ G|nU8oB2pDcf zq~p?y*O5IC(e>Eq0^pJa$hDKBEzVqoq#E4N=!fvQC)+ &;@>W; +DLC1W6QwoMmV$-U1_a5(^5lcqbLit_#UUoKAU*FUp)tkSyva zImZD^wxoIH1|uYkdOFpR>`bmYUfNcMt0`!*opP2o>g09CEzh>Q$yM3dS#(j{^cXE- zb|lCJ{U&EbvcQVw^t7_2 QO`Q;*_V&cnWL3BZP+?!vr3sjVqHVzpuR#QCFJ~r`i)`INkxIVOUbt+$u@2Z zEp0?D (x5(TFXTF 9@0rgZ(8wdd_1mtWs9f;j*@pbVkj8qQXl0tyw)A)hc$c6lZyw|v zg3V@wo_*^IMn#)E>|J^h-D4M0ZQUhpYTud#djnF0Vsa^7anE!wTaCkb^i=%n+BV$N z=+L(T Pnj wv)K|RvMiJek7gFsV__k4Z z!U*kgDtM*s?#QX@-f8qI(us4Iphewe#AAyQhz@;=0bWl1 CRhITCZPps)z <{Br`Rw7`%WGnSY-OC`b zzmkkGv}{QNuNiGgMUkMZX1sv62s8!OX-ur?C _Dn2C{mShfzSC0(B;M=gNZ#H#)a26I7vP|`CVG?7 zyzuBPpN}ha17(ZJZL$8Sei;P(?BqpG9l;0A&14gRb5uHVId!{9y>=K?3|oD44pv)H zSE;{SH%X8S)Sch2T(=y#po3f*@0RaQlU&5IdKWR~5+oPdOSaP{U(%fDngT1kTmkO` zs-rb{Jx8uG6sFVRPcJ7wXnB{u=(W?0gO~sPsfKI#P({VQs;O1FZ^}XF*|!O;efM0R zV$O5|_TUGH&|bdJ@wX;0{FkGR)=H{9t5ia=c*a&KzKvp`U6-E4BBFa^o9{;U+Qd0` z!MVw!Lq%S^Y$*cfE4H+PsJuS{&6&xIpiW=|hK)^LGg_O~&X-Ugm9TsT9p}Prv%KY~ zEMzieybMXxUZ&b1xs=z=S^u?H-Mx70uVHITlD#n@7x>nmk~Z5K)7wvHqvZju% M7& Nm1As$ {?LmExQIvqT;ISnKxwxJ!sbr5RZQqy0jWJ$Hi*uHy zg56xP?fyHhY%n*evw<=YMJmz5Vmmp`!XV*}bEsNDbGDAk@JI+FqyKX0Q@&oIxVHcR z5CBO;K~(Loj_h3#J^Y+Mx&Oi|ANeOUUf!VI{ z~KFfe@Qvi3wk zmW|t8&2hoTs>L>&Q-^M)J=&>eVxiG=L!5oYVyT$g3Iwd1Y=z#aa|Hy{e?x-SCDc<- zJ^ZP*OjE5$g3sjQbJ$=Gr`w~!OClQB6*ryM@8$5VT;cr1BMEXTDXZXH*hw}R8g=rz z<797r%gJ@;m1&Y?bUq~8Jd(@WXm%RS7o3Zbz01vI&j(g^IVuIowmz~Qmi#g#+kBEG z3#OM`NY7_ hF>;A*Y#?9O!B|(P+bE>4u zaA@|>l~;X)Px iIMKhaI1m*+=;8U)3)=qE_c2*d=T25HCqpZoVnRTg|3+G z+Q=;~+XQuuHla9d wIU z_pW(qf{UzmYd{3+VjC*_pNXCm9gtu4UgGI3zgBa zVwb`iWLVK0Je3krz$L{5Sm~WQH&Czj#SIi6*rMj(?Oq}1Ch3ueV=)#q8?%=VtkHYO zu(?rT54C8QG$mYpqza#paRqxx79FBn%mxIbZhm)~WH)O*O|sZFHa-1lUbs)h$)S`) zvapq02>TR^>;eJdq(RRH!D1Ost!CyNmGLMC7UZm9q}2~Z0S-1+TJd7#s;&8k_|xg| zd+}|5y6d*vtsNze{J{UZZ#<0tPuBLKHZ+V@V+Ph 51p5ZC>mwBW_dmLU)w`j$bvlH$_`LaV9n z(0f24m9>(Jj{Nx!BwlA+$#@Pl0-g6cn7f9iS#!=xh6fu1wUwz+F*njG9g;GKuUu#$ zk7iQMv*F7>K)s7L-fpJ-U;dPjob$CW+Uh*y84M9`U*%Mcn(WNCEkHQsC{}~mlgF-U zlufwoKG-u^j6HJd^TzvDd(n_LQrvMgeiwZnn5~IKhrTtquB $N~ecjZpZ>{NWeckD`>f3Jl61(nt%XYZnKpPQFfOdf# zRhx tgNQB-{6>yMY0@Ca9r`QH*ux z#q{PP;5JuX)V5j#^64{|rLfW5My1u3T3*fu52H@0?(}N&I_1_Hw>nE&YuWQlUfoZR zK!8@^@)?D*QfMokL}R0cESPhsXux!GYQ={q=iVPTU8UKm9s1|t?|tF}b5grLm_7L5 z4@@;$chnAy7HtXIk?_s~RA2EG&14enro;Nez}dU$2KizoZnvmZT|By)4B5s})oH7V zS^I{T0x{fl1T>;-y3VL@6$qR-di0XKIjc=njo_=0-wY9wJy#nW(M`5>lAKpVvZ#mT zJTFH2Az;qQp&0z;L<}_>7%iM*fm-EIPTYlD?oEzO@tg^omC^BW484PVUS7)X_V7_( z`{diI`!4ugYkazB3N?v=DA)7k)#hLmpdx4YT=u$CZ?{jAF5MD)c3qfzdYwW&vlYmp zdq1q~cy1vA(V=f4xXY<-&P+deI876$fvQ`MR43?W_DNeuGV0DYo{{lMf@Cp+HXhkd zTV5|B+xg@|87sFE_0m=H$OWAwOZI%&PdCZJcCvRkuV9lS 9So# zqT@mmM54Q|CqQwp6m-~N2>EM8QG^8Ytbrs6a3O XJr llS(`w|GB=LJql z$kTg2_v!fauWe+u?5f_eWxAu{J_x8o5Lxp8a?d^@=N+5eStxW*HiY?x%4^%bU*$ng zJWbV2vkcmCM>?!QGJ_@pTG@{so@k65`a3@Jnd0=*usiveKl?{MGE>?AY4h9FGyz=- zCN+4Mpk*JHlOx%TmXRzR4%s{0j5^L29n@24)Y;yki5N69O}%$XBdc}h#rWnUppjzp zl}3##A@F--?yDhhp;(cvB))_BB)6@0a{KD))u{D4<@J^`8?=alk;Rvg;Gka0%s(XN z-9+ZSXIR5{sD060`iMRA?tD@EzrOMRv-ceUn%va=zo(h`%J#k9&v&l4f(w{tI55pI z&4i;FFx5bSK dkMi~zj?vsNB3v*z7@HX9YTlsdz_iF^Pqav~7dtru zpE+GznuM@TBY*PLiykAQlTsGF9!s$TlPSB|)zywB*C9c2RFo+p=bJ|)mhl0T=xnVx z1jQ~?qmFc-xaxbGIv%_%4Z6r4yZWU2e7hc$_9Em|;ao}eKcb=GFg&p^0Rc&gi6cmK zW^;=Ojf~7D%be4S$T5|r<7+CKbH!$uQOnMgHv4m)NTYos4L1;E?G*_iB>j0~z9>P+ zC0GPH@K-AlFs&5sWlw^@&YgxQQDGo+&_ NqIu>RuzF}omnAwSfo1zs zQVff*$i0o)ummts8wTnIl~T`_R-Ja{viRF`ws_+E;jbL3MV((gQgbj|0Z00`d^~#> z=b(ocAg_6`GqI3k T;c8wC!X~_IsZz&Ssux>>^vgkR&PE13sor%rM5 5n1tq*<3j9VV^ZBoPyh<;mOOC`E}*(=FgwPqP1Ir-|#m^Nj#C#(azP z@@9fS%!5?RorY^x)-%bt1ky}CnU_H2W?2QpKFA28N=W;}VRUNf&ciT1qtRrOmei~O zjcdrs-H9_b(qwU&e9O`;bZ7)L>eVXkwqcq=0V$oJz5SZrQ_uTFmVTK(x=+6MZ%p^P z? -x3 zymcYU-hvSZIM1?p|7a-!ig!y3@WLlSfSZey?*zxk>I7;g-H9}Z$NTgA%Fq4kiU>pm z%FjvpGod_vvpYVYNJIX3{`l}zGRCK!EG%+#S hIWa>gO0-5x0Kr9v4?#KbV?9wcjoTOcWwzz_d;O zJ0&d}H^b;pC0|5_Q!TzTRXyikyT(`tTw_XPf;e~BY)iimdwS_(3a+&{q+J&9kCq{j z_ejeM@Pa2nfJ<6K12R5VB%cD~V
iEeePah*PwQLcAh)geN5mHzPtl>Lq zQYsOwgh*YZu2f5mWu=-`P$P=WiSF@{mWfE*wbR3!hS{Y&3~(8dk=?8Tm7&cD8BPJ9 zrvsUh2MXmfU{eHQkvLO#Yj(bO%^!W{_AK)Xd7S-#hhCGQxYvY5gh2t-N(DhzJ8J#a zmLs5sP-(J)G)`%JyoiivEn0Y@`b|-pIYdq0RvLHVke)sv{B tBk~4gZ zP8E{6H2O~2>_K&ALgSUHhJ{D?SvfS;4H-OsHxcU-G03ebpJ` dSUe&X&YJ>ktT!0{mz;zMoLxU)GmEm~AE+R7?e~SBDz|(w6Gs!C=Gf6h1G+L(8 z_{1VIqc%^Jcf73m4(n?tGv7kJycr?jai0-P^PH$Q;0&?+d+v-Cw1Cn86cEh3rX z5ox$bC76cMYK3H3F9*qL!7icC^1Wh8a`ZcZ@lYc!0sR4-gh)x?V!$aM6JJiiv>K<& z1e^Jlr{gre3D}_^w=KrIcq$Ddnd^0ZeFUy`n;dsI83tu=8tYKIl(Y#K&-K`pB(c z^KU%y%+J=-C`nU?a=C~?sSUL%S2^s5k%Y7G%ynF8KBdWu5^40?Wbw<9uR5u|GiU$+ z5CBO;K~&6*G+W1eqm??g#Wlt`oQ422qWrT(2n?g$BI#c2qzFs}yok__98Hr7n U65Or%exKxdMF>NE}lN3&q*4igP5^u4u`W~PyKnu3N_4KL`| za2Cb(sH-F*>SQD`>Q{!y(P0tEj@rd(3S9u`lA_5kfOHgb_)r}~VQ0PCw)s=b@ps!X zgUggwUu&X{y-~!4%@G=M?#v6NB75%W%sO6jIy!cKI1|YiVTTQipD2DfYRNcS$78Kb zK6b~-u?#BF8NNpi{xHKkEkR%^yV8;Zz0gqvOp^i_ iC9~ z**`^EBX>TO&-m5|EJBx%>ZCaP2rc`=gJA=krDt<24Z7tH-O=PX2=v*^7$duw$Os%A zJ4_i{AH#h0iU>p+r0Kx&DMX{PUBAi>xR@|V$g?EgJZtY<-D&rmRi8d-%c(ymXGPw{ z`#!L5$OgZwDjq#;*?aU#jfQ;&XdKJf(Y#926(b@eNoJCMB=I77=C6)wLX?n=e+*lA zb(9)RU8ovsMP}AvbH`-G=WO)Eiicr=0r&;<4?OhuuQ;#VycaGqs$3=#E;4oeM0sYz z6B(5+GU}g=jH%KRw5g(J6=&eG<8_RDyw;SRRd+Pz;Ep`!(riO$h=oR1-JCy_D Yoiw^f$jMCnu57@|xw2j~9`VXb~A{i^!;dBterWm(#?`ydsT!*^VOHN@Q$6fQ&dZ zGGoqk$PBncK-a}px-m>db6&TN?Boc^@JJrKw9VEgpe^P)LBDKIl?!1xj{U79A`A=p zfW8+F=nY8$(*(lwNfkOal41{zM63T58@Bu)OT1DaXP2rrY`fK(fbcq)Fob?oI1wBo zl3^<%BhfkzG$0~J#VNm;G;K~IvrDh}RF+61pZe#UL>jV1WX7VkUO6>q%6TF(f{TcZ zBoUDj{s^ YTj9hYDIp7=zI zpjbx`j |g#l^5JLl**?EFjNBB@9H}H*CD<()a3zVai{*^h9e*-FL>$#R1=) z6Re~0Ub)~*+j5T4Y12%UK<7 zVWC#DI#ouyU^bI(Hfd*4L#;_O%_PrE(o9v)=D246OyuTiw9dJIygOZ-#;4p_r)d1C zSRxHK<@<6=a 1=+FLB?m~o!2E_&i+7!1JrtnsnfZOTurB7hsvm{&`=mH`0 zgS(eu58s^!>ZO6VVxYeaH|WA-A9_E5OQKsfY9$lLhf7KwHy#|QR$ DV^e7HGoD*r_JCgP2kF_Sdo z^UN-+!{5+k8=BYv;E}oh(2T?A$qC v==)S z0zK {uxmr5Yuhi|&ba6)?T #nl-2vdXe;Q`=_m^KKW-^ynmo${l+^{Y#*u |_&Z1QNCIW_WG zDxL;$e-7bZJ#da@g5=mc+_#en;d}GM6mY~ww${Dn=WFl%h%4nepZ+oM%y0 b}*~4lGC0u>w2*P!_7GU?A-MMYLx7hgRy}oo7HC_2{V%B<}XXS`8uu4D2!X zm*PJe9VKE3Fhm3*4VtbxB8_w+GF{Rc$BRf-{){s)p@{G~!R;pCWh|N&XW(eeEz`KL zkRU`oi}H__AwbkyCff_13W3nw7CW^VjYJ}oOc+LsOdiMRdAUU-o8%%5cV2{2h$>n- z6W$ZWiO7U$r(-tDYW7c-PtyqBTf@cNMAu+k9U&>8&IvP~1*nJ+gD1O!p~Rq8?73t0 zN$0-|kA3I&Gr2uIrK&A%cD7Mx5rN88%HpSZsA!EAz7G~*eiJ}=luQ)}gpn<*JMjyv zA9(3LP2*`8fd^kv>+D_qtFTlC!Gf))0GD5U_ JtL4^>5ro#=iZ@sDG)CXSP z`JBJKT7+ll8eNZjEu>eyB8?1=2F9`@m9%}DQYqj0Yg$dyNX-zB@-2-yk7^LdF?f8B zpuPL%t{vxmT+ZC>cQk}3zlFFv6aC65zLqHJ`w*^)8O)Q=)eg;x!QorShmxjcPI3(w zaY~@uATp1-HBU~BNL885)Znh9C;I_8LF{nT6%zpnuptQ&&V-mVsSX HG4OLeesc}$wx`S(`d}67q@sp9} zZ>BumWcieDGKHC^#I(E5iR6#wZsgan%Fao{Q_*xXc^Xc|;UFN 2?Iq?-v^LynTN5?X4Hw1gL0^ztEt6@CwOM=>5o&zTm~yvSC`MF#1#3Fi z{Ay N>eg`VWOjB3C;k_>m2(EA?JiJ@9ggB#&ol^JgqMDa(^B#GW)VM zZ!Sk&E0!==8s0Q>^RP3HO`VwXG=QbJ1dcM87=q$31>~oI)RBaBq)~vnYr9#vAcMbJ zihyaU^hFYWycFwT4(D~v^gILVD$#pf-KrdBQsJ4Vn~AJEjZoRDKX=ui6O{-iBWI`( zqXKHR1oc{ss1We9n`+%T)TocDK^yKy?@zZs=m{5}^O}$R2j$pYuDXN!f4e3`x?26z ze~?VU0gmPX3?aX?eo9%G^0Rouga)N@JGhQOp{tzLq0g1R*?r2n@AB`?Gq8?&IA`_+ zL6q1uMVfNMr;~-08SJLi+8-LU> R+Kup9< v7OT%w3TMDpN_T%GV!(Ts#J zk; { zv<^|o {S63L0%S+}de0B1GOgVr&D# zuRP6e*%N>sv(v`IIETjT4nj=qE&IZ94w9RtQx}w@u2KpipN3l~D XKz860Q0z)ZJbe*C4 zkc`BkVH7wCazBPI>K-Z)P8TX4+eE|B^X@b&i)Mdb&_ujEZZ@vUQJPJj=CqbrWkh9? zD`&+gPvcz5otu&x*illQ sMQ}ebBl3|yj@wcW`8qZ&DVJCTutRe(QOfsG>T*;}`Vj6fe(3e{OH|RN3 zEp&Ahw@|znJsASBXOBgpb3cL*y1LENsk>N;V7|+pZ+VK1lFH3V!*eCo>0;{$c-$^j zO!Yc2gpzX`?(yw->xJ)=9nY#xCA&(~LT$x8xob*hX;RW`VWvz=V_+qO8#d;Rg_+Z+ zj4TVS@a%V>yzcA83!k{M{{xl`k*Ob>ww_!~Ole5CPy&rdmRjMYQO?E$jXY?Cb0Yi* zv~#nBy40eF>A8l=Ji