@usex/mikrotik-mcp 3.35.0 → 3.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,7 +76,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
76
76
  `).length;return(0,J.jsxs)(`div`,{className:`cfgstudio`,children:[(0,J.jsxs)(`div`,{className:`cfg-toolbar`,children:[(0,J.jsx)(`span`,{className:`cfg-status ${A?`is-ok`:`is-bad`}`,children:l?`invalid JSON`:s.length?`${s.length} schema issue(s)`:`valid ✓`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`topo-btn`,onClick:()=>void F(),children:`Test devices`}),(0,J.jsx)(`button`,{className:`topo-btn`,onClick:()=>void I(),disabled:!A,children:`Preview diff`}),(0,J.jsx)(`select`,{className:`cfg-select`,value:S,onChange:e=>C(Number(e.target.value)),title:`Auto-revert window`,children:pY.map(([e,t])=>(0,J.jsx)(`option`,{value:t,children:e},t))}),(0,J.jsx)(`button`,{className:`topo-btn cfg-save`,onClick:()=>void L(),disabled:!A||!!_,children:`Save`}),(0,J.jsx)(`button`,{className:`topo-btn`,onClick:t,children:`Close`})]}),w&&(0,J.jsx)(`div`,{className:`cfg-msg`,children:w}),_&&(0,J.jsxs)(`div`,{className:`cfg-banner`,children:[(0,J.jsx)(`strong`,{children:`Applied.`}),` `,(_.rollbackMs??0)>0?(0,J.jsxs)(J.Fragment,{children:[`Reverting in `,(0,J.jsxs)(`span`,{className:`cfg-count`,children:[b,`s`]}),` unless you keep it.`]}):(0,J.jsx)(J.Fragment,{children:`Saved without an auto-revert window.`}),_.devicesChanged&&(0,J.jsxs)(`span`,{className:`cfg-warn`,children:[` `,`· device list changed — reconnect the MCP client to expose it to the model`]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`topo-btn cfg-save`,onClick:()=>void ee(),children:`Keep changes`}),(0,J.jsx)(`button`,{className:`topo-btn`,onClick:()=>void te(),children:`Revert now`})]}),(0,J.jsxs)(`div`,{className:`cfg-editor`,children:[(0,J.jsx)(`pre`,{className:`cfg-gutter`,"aria-hidden":`true`,ref:k,children:Array.from({length:R},(e,t)=>t+1).join(`
77
77
  `)}),(0,J.jsxs)(`div`,{className:`cfg-ta-wrap`,children:[(0,J.jsxs)(`pre`,{className:`cfg-hl`,"aria-hidden":`true`,ref:O,children:[rY(r),`
78
78
  `]}),(0,J.jsx)(`textarea`,{ref:E,className:`cfg-ta`,spellCheck:!1,wrap:`off`,value:r,onChange:e=>{i(e.target.value),j(e.currentTarget)},onKeyDown:N,onClick:()=>f(null),onScroll:e=>{let t=e.currentTarget;O.current&&(O.current.scrollTop=t.scrollTop,O.current.scrollLeft=t.scrollLeft),k.current&&(k.current.scrollTop=t.scrollTop)}}),d&&(0,J.jsx)(`div`,{className:`cfg-ac`,style:{left:d.x,top:d.y},children:d.items.map((e,t)=>(0,J.jsx)(`div`,{className:`cfg-ac-item${t===d.index?` is-sel`:``}`,onMouseDown:t=>{t.preventDefault(),M(e)},children:e},e))})]})]}),Object.keys(p).length>0&&(0,J.jsx)(`div`,{className:`cfg-chips`,children:Object.entries(p).map(([e,t])=>(0,J.jsxs)(`span`,{className:`cfg-chip ${t.ok?`is-ok`:`is-bad`}`,children:[t.ok?`●`:`○`,` `,e,`: `,t.label]},e))}),(l||s.length>0)&&(0,J.jsx)(`div`,{className:`cfg-errors`,children:l?(0,J.jsxs)(`div`,{className:`cfg-err`,children:[`JSON: `,l]}):s.slice(0,12).map((e,t)=>(0,J.jsxs)(`div`,{className:`cfg-err`,children:[(0,J.jsx)(`code`,{children:e.path}),` — `,e.message]},t))}),h&&(0,J.jsxs)(`div`,{className:`cfg-preview`,children:[(0,J.jsxs)(`div`,{className:`cfg-preview__hd`,children:[(0,J.jsx)(`strong`,{children:`Diff vs current`}),(0,J.jsx)(`span`,{className:`muted`,children:h.summary?.changed?`+${h.summary.added} / -${h.summary.removed}`:`no changes`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`topo-btn`,onClick:()=>g(null),children:`✕`})]}),(0,J.jsx)(`pre`,{className:`cfg-diff`,children:(h.unified||`(identical)`).split(`
79
- `).map((e,t)=>(0,J.jsx)(`div`,{className:e.startsWith(`+`)?`d-add`:e.startsWith(`-`)?`d-del`:e.startsWith(`@@`)?`d-hunk`:``,children:e||` `},t))})]})]})}function hY({x:e,y:t,text:n,className:r,textClassName:i,padX:a=9,fontSize:o=9}){let s=(0,v.useRef)(null),[c,l]=(0,v.useState)(()=>n.length*o*.62);(0,v.useLayoutEffect)(()=>{let e=s.current?.getComputedTextLength();e&&e>0&&l(e)},[n,o]);let u=Math.round(c+a*2);return(0,J.jsxs)(`g`,{transform:`translate(${e.toFixed(1)},${t.toFixed(1)})`,children:[(0,J.jsx)(`rect`,{className:r,x:-u/2,y:-9,rx:9,width:u,height:18}),(0,J.jsx)(`text`,{ref:s,className:i,x:0,y:3.5,textAnchor:`middle`,fontSize:o,children:n})]})}function gY(e){return e.reachable===!0?{label:`online`,color:`#d4d4d8`}:e.reachable===!1?{label:`offline`,color:`#f87171`}:{label:`checking…`,color:`#71717a`}}function _Y(e){let t=0;for(let n=0;n<e.length;n++)t=Math.imul(t,31)+e.charCodeAt(n)>>>0;return`hsl(${t%360} 70% 62%)`}function vY(e,t,n,r){let i=1-r;return{x:i*i*e.x+2*i*r*t.x+r*r*n.x,y:i*i*e.y+2*i*r*t.y+r*r*n.y}}function yY(e,t,n,r){let i=1-r,a=2*i*(t.x-e.x)+2*r*(n.x-t.x),o=2*i*(t.y-e.y)+2*r*(n.y-t.y);return Math.atan2(o,a)*180/Math.PI}var bY=`#e4e4e7`,xY=`#d4d4d8`,SY=`#f59e0b`;function CY(e){return e.jumpVia?e.jumpVia:e.jumpHost?`${e.jumpHost.host}:${e.jumpHost.port}`:null}var wY=18,TY=e=>e.length>wY?`${e.slice(0,wY-1)}…`:e,EY=e=>Math.max(23,Math.min(70,e.length*3.1+12));function DY({payload:e,pulses:t}){let n=e.devices,r=Math.max(1,n.length),i=n.map(e=>{let t=TY(e.name);return{d:e,label:t,r:EY(t)}}),a=Math.max(23,...i.map(e=>e.r)),o=r>1?(a+18)/Math.sin(Math.PI/r):0,s=Math.max(110+a,o,120),c=a+60,l=Math.max(700,Math.round((s+a+40)*2)),u=Math.round((s+c)*2),d=l/2,f=u/2,p={x:d,y:f},m=i.map((e,t)=>{let n=t/r*Math.PI*2-Math.PI/2+(r%2==0?Math.PI/r:0);return{...e,i:t,x:d+s*Math.cos(n),y:f+s*Math.sin(n)}});return(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`svg`,{className:`conn`,viewBox:`0 0 ${l} ${u}`,width:`100%`,height:u,preserveAspectRatio:`xMidYMid meet`,children:[(0,J.jsxs)(`defs`,{children:[(0,J.jsxs)(`radialGradient`,{id:`conn-hub`,cx:`0.5`,cy:`0.34`,r:`0.75`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:`#fafafa`}),(0,J.jsx)(`stop`,{offset:`0.6`,stopColor:`#d4d4d8`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:`#a1a1aa`})]}),(0,J.jsxs)(`radialGradient`,{id:`conn-orb`,cx:`0.5`,cy:`0.32`,r:`0.85`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:`#27272a`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:`#18181b`})]}),(0,J.jsxs)(`radialGradient`,{id:`conn-burst`,cx:`0.5`,cy:`0.5`,r:`0.5`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:`#fafafa`}),(0,J.jsx)(`stop`,{offset:`0.5`,stopColor:`#e4e4e7`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:`#a1a1aa`,stopOpacity:`0`})]}),(0,J.jsxs)(`linearGradient`,{id:`conn-tunnel-grad`,x1:`0`,y1:`0`,x2:`1`,y2:`0`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:SY,stopOpacity:`0.15`}),(0,J.jsx)(`stop`,{offset:`0.5`,stopColor:SY,stopOpacity:`0.95`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:SY,stopOpacity:`0.15`})]}),(0,J.jsxs)(`filter`,{id:`conn-tunnel-glow`,x:`-40%`,y:`-40%`,width:`180%`,height:`180%`,children:[(0,J.jsx)(`feGaussianBlur`,{stdDeviation:`3`,result:`b`}),(0,J.jsxs)(`feMerge`,{children:[(0,J.jsx)(`feMergeNode`,{in:`b`}),(0,J.jsx)(`feMergeNode`,{in:`SourceGraphic`})]})]})]}),[.5,.78,1].map((e,t)=>(0,J.jsx)(`circle`,{className:`conn-grid`,cx:d,cy:f,r:s*e},`g-${t}`)),[0,1,2].map(e=>(0,J.jsx)(`circle`,{className:`conn-sonar`,cx:d,cy:f,style:{animationDelay:`${e*1.1}s`}},`s-${e}`)),m.map(({d:e,i:n,x:r,y:i})=>{let a=gY(e.status),o=e.status.reachable===!0,s=e.status.reachable==null,c={x:r,y:i},l=(d+r)/2,u=(f+i)/2,m=r-d,h=i-f,g=Math.hypot(m,h)||1,_=-h/g,v=m/g,y={x:l+_*16,y:u+v*16},b={x:l-_*16,y:u-v*16},x=`M${d},${f} Q${y.x.toFixed(1)},${y.y.toFixed(1)} ${r.toFixed(1)},${i.toFixed(1)}`,S=`M${d},${f} Q${b.x.toFixed(1)},${b.y.toFixed(1)} ${r.toFixed(1)},${i.toFixed(1)}`,C=`M${d},${f} Q${l.toFixed(1)},${u.toFixed(1)} ${r.toFixed(1)},${i.toFixed(1)}`,w=vY(p,y,c,.52),T=yY(p,y,c,.52),E=vY(p,b,c,.48),D=yY(p,b,c,.48)+180,O=t[e.name]??0;return(0,J.jsxs)(`g`,{children:[(0,J.jsx)(`path`,{id:`conn-cmd-${n}`,className:`conn-link`,d:x,stroke:o?bY:a.color,strokeOpacity:o?.5:.32,strokeDasharray:s?`2 8`:o?void 0:`6 7`}),(0,J.jsx)(`path`,{id:`conn-res-${n}`,className:`conn-link`,d:S,stroke:o?xY:a.color,strokeOpacity:o?.5:.18,strokeDasharray:o?void 0:`6 7`}),o&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`path`,{className:`conn-flow`,d:x,stroke:`#e4e4e7`}),(0,J.jsx)(`circle`,{className:`conn-packet`,r:3.2,fill:`#e4e4e7`,children:(0,J.jsx)(`animateMotion`,{dur:`2.6s`,repeatCount:`indefinite`,calcMode:`linear`,children:(0,J.jsx)(`mpath`,{href:`#conn-cmd-${n}`})})}),(0,J.jsx)(`rect`,{className:`conn-packet`,x:-2.6,y:-2.6,width:5.2,height:5.2,fill:`#d4d4d8`,transform:`rotate(45)`,children:(0,J.jsx)(`animateMotion`,{dur:`2.6s`,begin:`0.9s`,repeatCount:`indefinite`,calcMode:`linear`,keyPoints:`1;0`,keyTimes:`0;1`,children:(0,J.jsx)(`mpath`,{href:`#conn-res-${n}`})})}),(0,J.jsx)(`path`,{className:`conn-chevron`,d:`M-4,-3 L4,0 L-4,3`,stroke:`#e4e4e7`,transform:`translate(${w.x.toFixed(1)},${w.y.toFixed(1)}) rotate(${T.toFixed(1)})`}),(0,J.jsx)(`path`,{className:`conn-chevron`,d:`M-4,-3 L4,0 L-4,3`,stroke:`#d4d4d8`,transform:`translate(${E.x.toFixed(1)},${E.y.toFixed(1)}) rotate(${D.toFixed(1)})`})]}),O>0&&(0,J.jsx)(`g`,{children:(0,J.jsxs)(`circle`,{r:6,fill:`url(#conn-burst)`,children:[(0,J.jsx)(`animateMotion`,{dur:`1.1s`,repeatCount:`1`,calcMode:`linear`,keyPoints:`0;1;0`,keyTimes:`0;0.5;1`,path:C}),(0,J.jsx)(`animate`,{attributeName:`opacity`,values:`0;1;1;0`,keyTimes:`0;0.1;0.85;1`,dur:`1.1s`,repeatCount:`1`,fill:`freeze`})]})},`burst-${e.name}-${O}`)]},`l-${e.name}`)}),m.map(e=>{let t=e.d.jumpVia,n=t?m.find(e=>e.d.name===t):void 0,r=!n&&e.d.jumpHost?e.d.jumpHost:void 0;if(!n&&!r)return null;let i={x:e.x,y:e.y},a=Math.hypot(e.x-d,e.y-f)||1,o=(e.x-d)/a,s=(e.y-f)/a,c=n?{x:n.x,y:n.y}:{x:e.x+o*(e.r+46),y:e.y+s*(e.r+46)},l=(c.x+i.x)/2,u=(c.y+i.y)/2,p=Math.hypot(l-d,u-f)||1,h=n?50:16,g={x:l+(l-d)/p*h,y:u+(u-f)/p*h},_=`M${c.x.toFixed(1)},${c.y.toFixed(1)} Q${g.x.toFixed(1)},${g.y.toFixed(1)} ${i.x.toFixed(1)},${i.y.toFixed(1)}`,v=CY(e.d)??``,y=vY(c,g,i,.5),b=`conn-tunnel-${e.i}`;return(0,J.jsxs)(`g`,{className:`conn-tunnel-g`,children:[(0,J.jsx)(`path`,{className:`conn-tunnel-halo`,d:_,stroke:SY}),(0,J.jsx)(`path`,{id:b,className:`conn-tunnel`,d:_,stroke:`url(#conn-tunnel-grad)`,filter:`url(#conn-tunnel-glow)`}),r&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`circle`,{className:`conn-tunnel-sat`,cx:c.x,cy:c.y,r:11,fill:`#1c1917`,stroke:`#f59e0b`}),(0,J.jsx)(`text`,{x:c.x,y:c.y+3.5,textAnchor:`middle`,fontSize:11,children:`🛡️`})]}),(0,J.jsxs)(`text`,{className:`conn-tunnel-lock`,fontSize:13,textAnchor:`middle`,children:[`🔒`,(0,J.jsx)(`animateMotion`,{dur:`2.4s`,repeatCount:`indefinite`,calcMode:`linear`,rotate:`auto`,children:(0,J.jsx)(`mpath`,{href:`#${b}`})})]}),(0,J.jsx)(hY,{x:y.x,y:y.y,text:`⤳ via ${v}`,className:`conn-tunnel-badge`,textClassName:`conn-tunnel-badge-tx`})]},`jump-${e.d.name}`)}),(0,J.jsx)(`circle`,{className:`conn-hub-glow`,cx:d,cy:f,r:42}),(0,J.jsx)(`circle`,{className:`conn-hub-ring`,cx:d,cy:f,r:37}),(0,J.jsx)(`circle`,{cx:d,cy:f,r:29,fill:`url(#conn-hub)`,stroke:`#71717a`,strokeWidth:1.5}),(0,J.jsx)(`text`,{x:d,y:f-4,textAnchor:`middle`,fill:`#09090b`,fontSize:12,fontWeight:700,children:`LLM`}),(0,J.jsx)(`text`,{x:d,y:f+8,textAnchor:`middle`,fill:`#3f3f46`,fontSize:8,fontWeight:600,children:`⇄ MCP`}),(0,J.jsx)(`text`,{x:d,y:f+18,textAnchor:`middle`,fill:`#3f3f46`,fontSize:7.5,fontWeight:600,children:`server`}),m.map(({d:e,x:t,y:n,r,label:i})=>{let a=gY(e.status),o=e.status.reachable===!0,s=o?`${e.status.latencyMs??`?`} ms`:a.label,c=_Y(e.name);return(0,J.jsxs)(`g`,{className:`conn-node`,children:[o&&(0,J.jsx)(`circle`,{className:`conn-node-halo`,cx:t,cy:n,r:r+1,stroke:c}),(0,J.jsx)(`circle`,{cx:t,cy:n,r,fill:`url(#conn-orb)`}),(0,J.jsx)(`circle`,{cx:t,cy:n,r,fill:c,opacity:.16}),(0,J.jsx)(`circle`,{cx:t,cy:n,r,fill:`none`,stroke:c,strokeWidth:2.5}),(0,J.jsx)(`circle`,{className:o?`conn-blink`:void 0,cx:t+r*.7,cy:n-r*.7,r:4.5,fill:a.color,stroke:`#0a121b`,strokeWidth:1.5}),(0,J.jsx)(`text`,{x:t,y:n+3.5,textAnchor:`middle`,fill:`#fafafa`,fontSize:10,fontWeight:600,children:i}),(0,J.jsx)(`text`,{x:t,y:n+r+14,textAnchor:`middle`,fill:`#a1a1aa`,fontSize:9,children:e.address??e.host}),(0,J.jsx)(`text`,{x:t,y:n+r+26,textAnchor:`middle`,fill:a.color,fontSize:9,fontWeight:600,children:s})]},`n-${e.name}`)})]}),(0,J.jsxs)(`div`,{className:`conn-legend`,children:[(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:bY}}),` command · LLM → device`]}),(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:xY}}),` response · device → LLM`]}),(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:`#e4e4e7`}}),` live call (round-trip)`]}),e.devices.some(e=>CY(e))&&(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:`#f59e0b`}}),` 🔒 SSH jump tunnel (ProxyJump)`]})]})]})}function OY({d:e}){let t=gY(e.status),n=e.status.reachable===!0?`${t.label} · ${e.status.latencyMs??`?`}ms${e.status.version?` · v${e.status.version}`:``}`:e.status.reachable===!1?`${t.label}${e.status.error?` · ${e.status.error}`:``}`:t.label,r=_Y(e.name);return(0,J.jsxs)(`div`,{className:`card dev-card`,style:{borderLeft:`3px solid ${r}`},children:[(0,J.jsxs)(`div`,{className:`dev-card__top`,children:[(0,J.jsx)(`span`,{className:`dot`,style:{background:r},title:`device colour`}),(0,J.jsx)(`span`,{className:`dev-card__name`,children:e.name}),e.isDefault&&(0,J.jsx)(Mc,{type:`accent`,children:`default`}),(0,J.jsx)(`span`,{className:`dot dot--status`,style:{background:t.color},title:t.label}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`span`,{className:`chip`,children:e.authMode})]}),(0,J.jsxs)(`div`,{className:`dev-card__meta`,children:[(0,J.jsx)(`span`,{children:e.mac?`mac`:`host`}),(0,J.jsx)(`b`,{children:e.address??`${e.host}:${e.port}`}),(0,J.jsx)(`span`,{children:`user`}),(0,J.jsx)(`b`,{children:e.username}),(0,J.jsx)(`span`,{children:`status`}),(0,J.jsx)(`b`,{style:{color:t.color},children:n}),(0,J.jsx)(`span`,{children:`activity`}),(0,J.jsxs)(`b`,{children:[e.activity.calls,` calls · `,e.activity.errors,` err`,e.activity.avgMs?` · ${xc(e.activity.avgMs)} avg`:``]}),e.description&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`span`,{children:`note`}),(0,J.jsx)(`b`,{children:e.description})]})]}),CY(e)&&(0,J.jsxs)(`div`,{className:`jump-route`,title:`Reached over SSH through the bastion ${CY(e)} (ProxyJump) — no port exposed on ${e.name}.`,children:[(0,J.jsx)(`span`,{className:`jump-route__hop jump-route__hop--src`,title:`MCP server`,children:`🛰️`}),(0,J.jsx)(`span`,{className:`jump-route__wire`}),(0,J.jsxs)(`span`,{className:`jump-route__hop jump-route__hop--bastion`,children:[`🛡️ `,CY(e),e.jumpVia?(0,J.jsx)(`i`,{className:`jump-route__tag`,children:`jump`}):null]}),(0,J.jsx)(`span`,{className:`jump-route__wire jump-route__wire--enc`,children:(0,J.jsx)(`span`,{className:`jump-route__lock`,"aria-hidden":!0,children:`🔒`})}),(0,J.jsxs)(`span`,{className:`jump-route__hop jump-route__hop--dest`,style:{borderColor:r},title:e.name,children:[`📡 `,e.name]})]})]})}function kY({event:e,onClose:t}){let[n,r]=(0,v.useState)(sY);return(0,J.jsx)(`div`,{className:`overlay`,onClick:e=>e.target===e.currentTarget&&t(),children:(0,J.jsxs)(`div`,{className:`sheet`,children:[(0,J.jsxs)(`div`,{className:`sheet__hd`,children:[(0,J.jsx)(`span`,{className:`risk risk-${e.risk}`,children:e.risk}),(0,J.jsxs)(`h3`,{className:`sheet__tool`,children:[e.tool,(0,J.jsx)(bc,{text:e.tool,className:`iconbtn`,icon:!0,title:`Copy tool name`})]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(Ac,{type:`secondary`,size:`sm`,onClick:t,children:`✕ Close`})]}),(0,J.jsxs)(`div`,{className:`kv__body`,children:[(0,J.jsx)(`div`,{className:`kv__k`,children:`title`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.title}),(0,J.jsx)(`div`,{className:`kv__k`,children:`time`}),(0,J.jsx)(`div`,{className:`kv__v`,children:new Date(e.ts).toLocaleString(void 0,{hour12:!1})}),(0,J.jsx)(`div`,{className:`kv__k`,children:`device`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.device??`—`}),(0,J.jsx)(`div`,{className:`kv__k`,children:`transport`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.transport??`—`}),(0,J.jsx)(`div`,{className:`kv__k`,children:`duration`}),(0,J.jsx)(`div`,{className:`kv__v`,children:xc(e.durationMs)}),(0,J.jsx)(`div`,{className:`kv__k`,children:`status`}),(0,J.jsx)(`div`,{className:`kv__v`,children:(0,J.jsx)(`span`,{className:e.isError?`status-err`:`status-ok`,children:e.isError?`error`:`ok`})}),(0,J.jsx)(`div`,{className:`kv__k`,children:`output size`}),(0,J.jsxs)(`div`,{className:`kv__v`,children:[Sc(e.outputBytes),e.truncated?` (truncated)`:``]}),(0,J.jsx)(`div`,{className:`kv__k`,children:`structured`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.hasStructured?`yes (renders an MCP App view)`:`no`})]}),e.error&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`h2`,{className:`muted`,children:`ERROR`}),(0,J.jsx)(`pre`,{className:`body`,style:{color:`var(--mt-bad)`},children:e.error})]}),(0,J.jsxs)(`div`,{className:`sheet__hd`,children:[(0,J.jsx)(`h2`,{className:`muted`,style:{margin:0},children:`INPUT`}),(0,J.jsx)(`span`,{style:{flex:1}}),e.input&&(0,J.jsx)(Ac,{type:`secondary`,size:`sm`,ghost:!0,onClick:()=>r(e=>{let t=!e;return cY(t),t}),title:n?`Showing pretty-printed JSON — click for raw`:`Showing raw JSON — click to pretty-print`,children:n?`✦ Pretty`:`{ } Raw`}),(0,J.jsx)(bc,{text:e.input,title:`Copy input JSON`})]}),e.input?(0,J.jsx)(uY,{value:lY(e.input,n)}):(0,J.jsx)(`pre`,{className:`body`,children:`—`}),(0,J.jsxs)(`div`,{className:`sheet__hd`,children:[(0,J.jsx)(`h2`,{className:`muted`,style:{margin:0},children:`OUTPUT`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(bc,{text:e.output,title:`Copy output`})]}),(0,J.jsx)(`pre`,{className:`body ros`,children:e.output?aY(e.output):`—`})]})})}var AY=e=>e==null?`?`:Sc(e);function jY({d:e}){let t=e.status,n=e.history??[];return t.reachable===!0||n.length>0?(0,J.jsxs)(`div`,{className:`card health-card`,children:[(0,J.jsxs)(`div`,{className:`health-card__hd`,children:[(0,J.jsx)(Nc,{color:gY(t).color}),(0,J.jsx)(`span`,{className:`dev-card__name`,children:e.name}),e.isDefault&&(0,J.jsx)(Mc,{type:`accent`,children:`default`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(Mc,{type:t.version?`success`:`default`,children:t.version?`v${t.version}`:`—`})]}),(0,J.jsxs)(`div`,{className:`health-card__sub muted`,children:[t.boardName??`router`,t.architecture?` · ${t.architecture}`:``,t.cpuCount?` · ${t.cpuCount} cpu`:``,t.uptime?` · up ${t.uptime}`:``]}),(0,J.jsxs)(`div`,{className:`health-card__gauges`,children:[(0,J.jsx)(HJ,{value:t.cpuLoad,label:`CPU`,color:Ec.cpu}),(0,J.jsx)(HJ,{value:t.memUsedPct,label:`MEM`,color:Ec.mem}),(0,J.jsx)(HJ,{value:t.hddUsedPct,label:`DISK`,color:Ec.disk})]}),(0,J.jsxs)(`div`,{className:`health-card__charts`,children:[(0,J.jsxs)(`div`,{className:`health-chart`,children:[(0,J.jsx)(`span`,{className:`health-chart__k`,children:`CPU load`}),(0,J.jsx)(VJ,{id:`${e.name}-cpu`,values:n.map(e=>e.cpuLoad),color:Ec.cpu,maxValue:100,unit:`%`})]}),(0,J.jsxs)(`div`,{className:`health-chart`,children:[(0,J.jsx)(`span`,{className:`health-chart__k`,children:`Memory used`}),(0,J.jsx)(VJ,{id:`${e.name}-mem`,values:n.map(e=>e.memUsedPct),color:Ec.mem,maxValue:100,unit:`%`})]}),(0,J.jsxs)(`div`,{className:`health-chart`,children:[(0,J.jsx)(`span`,{className:`health-chart__k`,children:`Probe latency`}),(0,J.jsx)(VJ,{id:`${e.name}-lat`,values:n.map(e=>e.latencyMs),color:Ec.latency,unit:`ms`})]})]}),(0,J.jsxs)(`div`,{className:`health-card__foot muted`,children:[`RAM `,AY(t.totalMemory&&t.freeMemory?t.totalMemory-t.freeMemory:void 0),` /`,` `,AY(t.totalMemory),` · free disk `,AY(t.freeHdd)]})]}):(0,J.jsxs)(`div`,{className:`card health-card health-card--na`,children:[(0,J.jsxs)(`div`,{className:`health-card__hd`,children:[(0,J.jsx)(Nc,{color:gY(t).color}),(0,J.jsx)(`span`,{className:`dev-card__name`,children:e.name}),e.isDefault&&(0,J.jsx)(Mc,{type:`accent`,children:`default`})]}),(0,J.jsx)(`p`,{className:`muted`,style:{margin:0},children:t.reachable===!1?`Offline — ${t.error??`unreachable`}`:e.mac?`Waiting for the first MAC-Telnet probe (these run every few minutes to avoid contending with tool calls)…`:`Waiting for the first health probe…`})]})}function MY(e,t){let n=(0,v.useRef)(e),r=(0,v.useRef)(t);n.current=e,r.current=t,(0,v.useEffect)(()=>{let e=!1,t=null,i=null,a=()=>{e||(i=new EventSource(dc(`/api/sse`)),i.addEventListener(`hello`,()=>r.current(`sse`)),i.addEventListener(`tool`,e=>{try{n.current(JSON.parse(e.data))}catch{}}),i.onerror=()=>{i&&i.readyState===EventSource.CONNECTING&&r.current(`off`)})},o=()=>{if(e)return;let i=location.protocol===`https:`?`wss`:`ws`;t=new WebSocket(dc(`${i}://${location.host}/api/stream`));let s=!1;t.onopen=()=>{s=!0,r.current(`ws`)},t.onerror=()=>t?.close(),t.onmessage=e=>{try{let t=JSON.parse(e.data);t.type===`event`&&t.event&&n.current(t.event)}catch{}},t.onclose=()=>{e||(r.current(`off`),s?setTimeout(o,2e3):a())}};return o(),()=>{e=!0,t?.close(),i?.close()}},[])}function NY(e){(0,v.useLayoutEffect)(()=>{let t=e.current;if(!t||window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;document.documentElement.classList.add(`js-motion`);let n=new WeakSet,r=e=>{n.has(e)||(n.add(e),Ri.set(e,{opacity:0,y:26}),q.create({trigger:e,start:`top 90%`,once:!0,onEnter:()=>Ri.to(e,{opacity:1,y:0,duration:.7,ease:`power3.out`})}))},i=e=>{for(let t of e.querySelectorAll(`.reveal`))r(t)};i(t);let a=new MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e instanceof Element&&(e.matches(`.reveal`)&&r(e),i(e))});a.observe(t,{childList:!0,subtree:!0});let o=setInterval(()=>q.refresh(),1200),s=setTimeout(()=>clearInterval(o),7e3);return()=>{a.disconnect(),clearInterval(o),clearTimeout(s);for(let e of q.getAll())e.kill();document.documentElement.classList.remove(`js-motion`)}},[e])}function PY({m:e,busy:t,onToggle:n}){return(0,J.jsxs)(`label`,{className:`mod-row`,"data-on":e.enabled?`1`:void 0,title:e.description,children:[(0,J.jsx)(`input`,{type:`checkbox`,checked:e.enabled,disabled:t,onChange:()=>n(e.slug,!e.enabled)}),(0,J.jsxs)(`span`,{className:`mod-row__main`,children:[(0,J.jsxs)(`span`,{className:`mod-row__name`,children:[e.label,(0,J.jsx)(`code`,{className:`mod-row__slug`,children:e.slug})]}),(0,J.jsx)(`span`,{className:`mod-row__desc muted`,children:e.description})]}),(0,J.jsxs)(`span`,{className:`mod-row__count muted`,children:[e.toolCount,` tool`,e.toolCount===1?``:`s`]})]})}function FY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(new Set),[i,a]=(0,v.useState)(null),[o,s]=(0,v.useState)(``),c=(0,v.useCallback)(()=>{fc(`/api/modules`).then(t).catch(()=>a(`could not load modules`))},[]);(0,v.useEffect)(()=>c(),[c]);let l=e=>t(t=>t?{...t,...e}:e),u=(0,v.useCallback)(async(e,n)=>{r(t=>new Set(t).add(e)),t(t=>t&&{...t,modules:t.modules.map(t=>t.slug===e?{...t,enabled:n}:t)});let i=await pc(`/api/modules/toggle`,{slug:e,enabled:n}).catch(()=>({error:`request failed`}));if(r(t=>{let n=new Set(t);return n.delete(e),n}),i.error||i.ok===!1){a(i.error??`toggle failed`),c();return}l(i);let o=i.persisted?`saved to config`:`applied live (not saved)`,s=i.warning?` ⚠ ${i.warning}`:``;a(`${e} ${n?`enabled`:`disabled`} — ${o}. Reconnect the MCP client (or restart the server) for the tool list to update.${s}`)},[c]),d=(0,v.useCallback)(async(e,t)=>{for(let n of e)await u(n,t)},[u]),f=(0,v.useMemo)(()=>{if(!e)return[];let t=o.trim().toLowerCase(),n=e=>!t||e.slug.toLowerCase().includes(t)||e.label.toLowerCase().includes(t)||e.group.toLowerCase().includes(t)||e.description.toLowerCase().includes(t),r=new Map;for(let t of e.modules){if(!n(t))continue;let e=r.get(t.group)??[];e.push(t),r.set(t.group,e)}return[...r.entries()].map(([e,t])=>({group:e,modules:t}))},[e,o]);if(!e)return(0,J.jsx)(`div`,{className:`muted`,children:`loading modules…`});let p=f.reduce((e,t)=>e+t.modules.length,0);return(0,J.jsx)(`section`,{className:`view`,children:(0,J.jsxs)(gc,{title:`Tool modules`,className:`reveal`,extra:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`span`,{className:`muted`,children:[e.enabledModules,`/`,e.total,` modules · `,e.enabledTools,`/`,e.totalTools,` `,`tools exposed`]}),(0,J.jsx)(`button`,{className:`btn`,onClick:c,style:{marginLeft:10},children:`↻ Refresh`})]}),children:[(0,J.jsxs)(`div`,{className:`legend`,style:{margin:`0 0 10px`},children:[(0,J.jsxs)(`span`,{children:[`writes to: `,(0,J.jsx)(`code`,{children:e.source?.path??`config file`})]}),(0,J.jsx)(`span`,{children:e.hasAllowList?`allow-list active`:`all modules on by default`})]}),(0,J.jsxs)(`p`,{className:`muted`,style:{margin:`0 0 12px`,fontSize:12},children:[`Toggle a module to expose or hide all of its tools. Disabling adds it to`,` `,(0,J.jsx)(`code`,{children:`tools.disabledModules`}),` in your config file; enabling removes it (or adds it to`,` `,(0,J.jsx)(`code`,{children:`tools.enabledModules`}),` when an allow-list is in force). Trimming the surface below ~150–200 tools makes every remaining tool reliably findable by the MCP client. The client must reconnect for changes to take effect.`]}),(0,J.jsxs)(`div`,{className:`mod-toolbar`,children:[(0,J.jsx)(`input`,{className:`input`,placeholder:`Filter modules by name, slug, group or description…`,value:o,onChange:e=>s(e.target.value)}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>void d(e.modules.filter(e=>!e.enabled).map(e=>e.slug),!0),children:`Enable all`}),` `,(0,J.jsx)(`button`,{className:`btn`,onClick:()=>void d(e.modules.filter(e=>e.enabled).map(e=>e.slug),!1),children:`Disable all`})]}),i&&(0,J.jsx)(`div`,{className:`cfg-msg`,style:{marginTop:12},children:i}),p===0?(0,J.jsxs)(`div`,{className:`muted`,style:{padding:12},children:[`No modules match “`,o,`”.`]}):(0,J.jsx)(`div`,{className:`mod-groups`,children:f.map(({group:e,modules:t})=>{let r=t.filter(e=>e.enabled).length,i=t.map(e=>e.slug);return(0,J.jsxs)(`div`,{className:`mod-group`,children:[(0,J.jsxs)(`div`,{className:`mod-group__hd`,children:[(0,J.jsx)(`h3`,{className:`mod-group__title`,children:e}),(0,J.jsxs)(`span`,{className:`muted`,children:[r,`/`,t.length,` on`]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`btn btn-sm`,onClick:()=>void d(i.filter(e=>{let n=t.find(t=>t.slug===e);return n?!n.enabled:!1}),!0),children:`Enable group`}),` `,(0,J.jsx)(`button`,{className:`btn btn-sm`,onClick:()=>void d(i.filter(e=>{let n=t.find(t=>t.slug===e);return n?n.enabled:!1}),!1),children:`Disable group`})]}),(0,J.jsx)(`div`,{className:`mod-list`,children:t.map(e=>(0,J.jsx)(PY,{m:e,busy:n.has(e.slug),onToggle:(e,t)=>void u(e,t)},e.slug))})]},e)})})]})})}var IY={TCP:`#e4e4e7`,UDP:`#d4d4d8`,ICMP:`#a1a1aa`,ICMPv6:`#a1a1aa`,ARP:`#e4e4e7`,IPv6:`#a1a1aa`},LY=e=>e&&IY[e]||`#71717a`;function RY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(!1);(0,v.useEffect)(()=>{let e=()=>void fc(`/api/capture/packets?limit=150`).then(t).catch(()=>{});e();let n=setInterval(e,1500);return()=>clearInterval(n)},[]);let i=e?.stats,a=e?.packets??[],o=async()=>{r(!0),await pc(`/api/capture/stop`,{}).catch(()=>{}),r(!1)};if(!i||!i.running&&i.packets===0)return(0,J.jsxs)(`div`,{className:`cap-idle muted`,children:[`No capture running. Start one with the `,(0,J.jsx)(`code`,{children:`start_packet_capture`}),` tool — point the device's TZSP stream at this host — and decoded packets stream in here live.`]});let s=Math.max(1,...Object.values(i.protocols));return(0,J.jsxs)(`div`,{className:`cap`,children:[(0,J.jsxs)(`div`,{className:`cap-bar`,children:[(0,J.jsx)(`span`,{className:`cap-dot${i.running?` is-on`:``}`}),(0,J.jsx)(`b`,{children:i.running?`capturing`:`stopped`}),(0,J.jsxs)(`span`,{className:`muted`,children:[`UDP `,i.port]}),(0,J.jsxs)(`span`,{className:`muted`,children:[wc(i.packets),` pkts · `,Sc(i.bytes)]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`a`,{className:`btn`,href:dc(`/api/capture/pcap`),download:`capture.pcap`,children:`⤓ pcap`}),(0,J.jsx)(`button`,{className:`btn btn-danger`,onClick:()=>void o(),disabled:n||!i.running,children:`■ Stop`})]}),(0,J.jsxs)(`div`,{className:`cap-cols`,children:[(0,J.jsxs)(`div`,{className:`cap-side`,children:[(0,J.jsx)(`div`,{className:`cap-h`,children:`Protocols`}),Object.entries(i.protocols).map(([e,t])=>(0,J.jsxs)(`div`,{className:`cap-pbar`,children:[(0,J.jsx)(`span`,{className:`cap-plabel`,style:{color:LY(e)},children:e}),(0,J.jsx)(`span`,{className:`cap-ptrack`,children:(0,J.jsx)(`i`,{style:{width:`${t/s*100}%`,background:LY(e)}})}),(0,J.jsx)(`span`,{className:`cap-pn`,children:t})]},e)),(0,J.jsx)(`div`,{className:`cap-h`,style:{marginTop:12},children:`Top talkers`}),i.topTalkers.length===0&&(0,J.jsx)(`div`,{className:`muted`,children:`—`}),i.topTalkers.map(e=>(0,J.jsxs)(`div`,{className:`cap-talker`,children:[(0,J.jsx)(`span`,{children:e.addr}),(0,J.jsx)(`b`,{children:e.count})]},e.addr))]}),(0,J.jsx)(`div`,{className:`cap-list`,children:a.length===0?(0,J.jsx)(`div`,{className:`muted`,style:{padding:10},children:`waiting for packets…`}):a.map((e,t)=>(0,J.jsxs)(`div`,{className:`cap-row`,children:[(0,J.jsx)(`span`,{className:`cap-tt`,children:Cc(e.ts)}),(0,J.jsx)(`span`,{className:`cap-proto`,style:{color:LY(e.protocol)},children:e.protocol??e.ethType}),(0,J.jsx)(`span`,{className:`cap-len`,children:e.len}),(0,J.jsx)(`span`,{className:`cap-info`,children:e.info})]},t))})]})]})}function zY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(null),[i,a]=(0,v.useState)(null),[o,s]=(0,v.useState)(null),c=(0,v.useCallback)(()=>{fc(`/api/s3/list`).then(t).catch(()=>t({configured:!1,objects:[]}))},[]);(0,v.useEffect)(()=>c(),[c]);let l=e=>{fc(`/api/s3/presign?key=${encodeURIComponent(e)}`).then(e=>{e.url?window.open(e.url,`_blank`,`noopener`):s(`could not generate download link`)}).catch(()=>s(`could not generate download link`))},u=async e=>{a(e);let t=await pc(`/api/s3/delete`,{key:e}).catch(()=>({error:`request failed`}));a(null),r(null),t.ok?(s(`Deleted ${e}`),c()):s(t.error??`delete failed`)};return e?e.configured?(0,J.jsx)(`section`,{className:`view`,children:(0,J.jsxs)(gc,{title:`S3 backups`,className:`reveal`,extra:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`span`,{className:`muted`,children:[e.target,` · `,e.objects.length,` object`,e.objects.length===1?``:`s`,e.truncated?` (truncated)`:``]}),(0,J.jsx)(`button`,{className:`btn`,onClick:c,style:{marginLeft:10},children:`↻ Refresh`})]}),children:[o&&(0,J.jsx)(`div`,{className:`cfg-msg`,children:o}),e.objects.length===0?(0,J.jsxs)(`div`,{className:`muted`,style:{padding:12},children:[`No objects in the bucket. Upload one with the `,(0,J.jsx)(`code`,{children:`upload_backup_to_s3`}),` tool.`]}):(0,J.jsx)(`div`,{className:`feedwrap`,children:(0,J.jsxs)(`table`,{className:`feed`,children:[(0,J.jsx)(`thead`,{children:(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`th`,{children:`key`}),(0,J.jsx)(`th`,{className:`num`,children:`size`}),(0,J.jsx)(`th`,{children:`modified`}),(0,J.jsx)(`th`,{style:{width:200},children:`actions`})]})}),(0,J.jsx)(`tbody`,{children:e.objects.map(e=>(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`td`,{children:e.key}),(0,J.jsx)(`td`,{className:`num`,children:Sc(e.size)}),(0,J.jsx)(`td`,{children:e.lastModified?new Date(e.lastModified).toLocaleString(void 0,{hour12:!1}):`—`}),(0,J.jsxs)(`td`,{onClick:e=>e.stopPropagation(),children:[(0,J.jsx)(`button`,{className:`btn`,onClick:()=>l(e.key),children:`⤓ Download`}),` `,n===e.key?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`button`,{className:`btn btn-danger`,disabled:i===e.key,onClick:()=>void u(e.key),children:`✓ Confirm`}),` `,(0,J.jsx)(`button`,{className:`btn`,onClick:()=>r(null),children:`Cancel`})]}):(0,J.jsx)(`button`,{className:`btn`,onClick:()=>r(e.key),children:`🗑 Delete`})]})]},e.key))})]})})]})}):(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`☁️`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`S3 is not configured`}),(0,J.jsxs)(`p`,{className:`feed-empty__sub`,children:[`Add an `,(0,J.jsx)(`code`,{children:`s3`}),` block (bucket + credentials) to your config to manage backup objects here.`]})]}):(0,J.jsx)(`div`,{className:`muted`,children:`loading S3 objects…`})}function BY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(``),[i,a]=(0,v.useState)(``),[o,s]=(0,v.useState)(null),[c,l]=(0,v.useState)(null),u=(0,v.useCallback)(()=>{fc(`/api/snapshots`).then(e=>t(e.snapshots)).catch(()=>t([]))},[]);(0,v.useEffect)(()=>u(),[u]);let d=e=>{fc(`/api/snapshot/${encodeURIComponent(e)}`).then(s).catch(()=>{})},f=()=>{!n||!i||pc(`/api/snapshots/diff`,{from:n,to:i}).then(l).catch(()=>{})};if(!e)return(0,J.jsx)(`div`,{className:`muted`,children:`loading snapshots…`});if(e.length===0)return(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`🕰️`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`No config snapshots yet`}),(0,J.jsxs)(`p`,{className:`feed-empty__sub`,children:[`Capture one with the `,(0,J.jsx)(`code`,{children:`capture_config_snapshot`}),` tool — then time-travel diff any two here.`]})]});let p=e.map(e=>(0,J.jsxs)(`option`,{value:e.id,children:[e.device,` · `,e.label??e.id,` · `,Cc(e.ts)]},e.id));return(0,J.jsxs)(`section`,{className:`view`,children:[(0,J.jsxs)(gc,{title:`Config snapshots`,className:`reveal`,extra:(0,J.jsx)(`button`,{className:`btn`,onClick:u,children:`↻ Refresh`}),children:[(0,J.jsxs)(`div`,{className:`toolbar`,style:{marginBottom:12},children:[(0,J.jsxs)(`select`,{className:`btn`,value:n,onChange:e=>r(e.target.value),children:[(0,J.jsx)(`option`,{value:``,children:`diff from…`}),p]}),(0,J.jsxs)(`select`,{className:`btn`,value:i,onChange:e=>a(e.target.value),children:[(0,J.jsx)(`option`,{value:``,children:`to…`}),p]}),(0,J.jsx)(`button`,{className:`btn is-active`,onClick:f,disabled:!n||!i,children:`Diff →`}),(0,J.jsxs)(`span`,{className:`muted`,children:[e.length,` snapshots`]})]}),(0,J.jsx)(`div`,{className:`feedwrap`,children:(0,J.jsxs)(`table`,{className:`feed`,children:[(0,J.jsx)(`thead`,{children:(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`th`,{children:`captured`}),(0,J.jsx)(`th`,{children:`device`}),(0,J.jsx)(`th`,{children:`label`}),(0,J.jsx)(`th`,{children:`version`}),(0,J.jsx)(`th`,{className:`num`,children:`lines`}),(0,J.jsx)(`th`,{className:`num`,children:`size`}),(0,J.jsx)(`th`,{children:`output`})]})}),(0,J.jsx)(`tbody`,{children:e.map(e=>(0,J.jsxs)(`tr`,{className:o?.id===e.id?`is-selected`:void 0,onClick:()=>d(e.id),children:[(0,J.jsx)(`td`,{children:Cc(e.ts)}),(0,J.jsx)(`td`,{children:e.device}),(0,J.jsx)(`td`,{children:e.label??`—`}),(0,J.jsx)(`td`,{children:e.rosVersion??`—`}),(0,J.jsx)(`td`,{className:`num`,children:e.lines}),(0,J.jsx)(`td`,{className:`num`,children:Sc(e.bytes)}),(0,J.jsx)(`td`,{className:`preview`,children:`view export →`})]},e.id))})]})})]}),c&&(0,J.jsx)(gc,{title:`Time-travel diff`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:c.summary.changed?`+${c.summary.added} / -${c.summary.removed}`:`identical`}),children:(0,J.jsx)(`pre`,{className:`cfg-diff`,children:(c.unified||`(identical)`).split(`
79
+ `).map((e,t)=>(0,J.jsx)(`div`,{className:e.startsWith(`+`)?`d-add`:e.startsWith(`-`)?`d-del`:e.startsWith(`@@`)?`d-hunk`:``,children:e||` `},t))})]})]})}function hY({x:e,y:t,text:n,className:r,textClassName:i,padX:a=9,fontSize:o=9}){let s=(0,v.useRef)(null),[c,l]=(0,v.useState)(()=>n.length*o*.62);(0,v.useLayoutEffect)(()=>{let e=s.current?.getComputedTextLength();e&&e>0&&l(e)},[n,o]);let u=Math.round(c+a*2);return(0,J.jsxs)(`g`,{transform:`translate(${e.toFixed(1)},${t.toFixed(1)})`,children:[(0,J.jsx)(`rect`,{className:r,x:-u/2,y:-9,rx:9,width:u,height:18}),(0,J.jsx)(`text`,{ref:s,className:i,x:0,y:3.5,textAnchor:`middle`,fontSize:o,children:n})]})}function gY(e){return e.reachable===!0?{label:`online`,color:`#d4d4d8`}:e.reachable===!1?{label:`offline`,color:`#f87171`}:{label:`checking…`,color:`#71717a`}}function _Y(e){let t=0;for(let n=0;n<e.length;n++)t=Math.imul(t,31)+e.charCodeAt(n)>>>0;return`hsl(${t%360} 70% 62%)`}function vY(e,t,n,r){let i=1-r;return{x:i*i*e.x+2*i*r*t.x+r*r*n.x,y:i*i*e.y+2*i*r*t.y+r*r*n.y}}function yY(e,t,n,r){let i=1-r,a=2*i*(t.x-e.x)+2*r*(n.x-t.x),o=2*i*(t.y-e.y)+2*r*(n.y-t.y);return Math.atan2(o,a)*180/Math.PI}var bY=`#e4e4e7`,xY=`#d4d4d8`,SY=`#f59e0b`;function CY(e){return e.jumpVia?e.jumpVia:e.jumpHost?`${e.jumpHost.host}:${e.jumpHost.port}`:null}var wY=18,TY=e=>e.length>wY?`${e.slice(0,wY-1)}…`:e,EY=e=>Math.max(23,Math.min(70,e.length*3.1+12));function DY({payload:e,pulses:t}){let n=e.devices,r=Math.max(1,n.length),i=n.map(e=>{let t=TY(e.name);return{d:e,label:t,r:EY(t)}}),a=Math.max(23,...i.map(e=>e.r)),o=r>1?(a+18)/Math.sin(Math.PI/r):0,s=Math.max(110+a,o,120),c=a+60,l=Math.max(700,Math.round((s+a+40)*2)),u=Math.round((s+c)*2),d=l/2,f=u/2,p={x:d,y:f},m=i.map((e,t)=>{let n=t/r*Math.PI*2-Math.PI/2+(r%2==0?Math.PI/r:0);return{...e,i:t,x:d+s*Math.cos(n),y:f+s*Math.sin(n)}});return(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`svg`,{className:`conn`,viewBox:`0 0 ${l} ${u}`,width:`100%`,height:u,preserveAspectRatio:`xMidYMid meet`,children:[(0,J.jsxs)(`defs`,{children:[(0,J.jsxs)(`radialGradient`,{id:`conn-hub`,cx:`0.5`,cy:`0.34`,r:`0.75`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:`#fafafa`}),(0,J.jsx)(`stop`,{offset:`0.6`,stopColor:`#d4d4d8`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:`#a1a1aa`})]}),(0,J.jsxs)(`radialGradient`,{id:`conn-orb`,cx:`0.5`,cy:`0.32`,r:`0.85`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:`#27272a`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:`#18181b`})]}),(0,J.jsxs)(`radialGradient`,{id:`conn-burst`,cx:`0.5`,cy:`0.5`,r:`0.5`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:`#fafafa`}),(0,J.jsx)(`stop`,{offset:`0.5`,stopColor:`#e4e4e7`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:`#a1a1aa`,stopOpacity:`0`})]}),(0,J.jsxs)(`linearGradient`,{id:`conn-tunnel-grad`,x1:`0`,y1:`0`,x2:`1`,y2:`0`,children:[(0,J.jsx)(`stop`,{offset:`0`,stopColor:SY,stopOpacity:`0.15`}),(0,J.jsx)(`stop`,{offset:`0.5`,stopColor:SY,stopOpacity:`0.95`}),(0,J.jsx)(`stop`,{offset:`1`,stopColor:SY,stopOpacity:`0.15`})]}),(0,J.jsxs)(`filter`,{id:`conn-tunnel-glow`,x:`-40%`,y:`-40%`,width:`180%`,height:`180%`,children:[(0,J.jsx)(`feGaussianBlur`,{stdDeviation:`3`,result:`b`}),(0,J.jsxs)(`feMerge`,{children:[(0,J.jsx)(`feMergeNode`,{in:`b`}),(0,J.jsx)(`feMergeNode`,{in:`SourceGraphic`})]})]})]}),[.5,.78,1].map((e,t)=>(0,J.jsx)(`circle`,{className:`conn-grid`,cx:d,cy:f,r:s*e},`g-${t}`)),[0,1,2].map(e=>(0,J.jsx)(`circle`,{className:`conn-sonar`,cx:d,cy:f,style:{animationDelay:`${e*1.1}s`}},`s-${e}`)),m.map(({d:e,i:n,x:r,y:i})=>{let a=gY(e.status),o=e.status.reachable===!0,s=e.status.reachable==null,c={x:r,y:i},l=(d+r)/2,u=(f+i)/2,m=r-d,h=i-f,g=Math.hypot(m,h)||1,_=-h/g,v=m/g,y={x:l+_*16,y:u+v*16},b={x:l-_*16,y:u-v*16},x=`M${d},${f} Q${y.x.toFixed(1)},${y.y.toFixed(1)} ${r.toFixed(1)},${i.toFixed(1)}`,S=`M${d},${f} Q${b.x.toFixed(1)},${b.y.toFixed(1)} ${r.toFixed(1)},${i.toFixed(1)}`,C=`M${d},${f} Q${l.toFixed(1)},${u.toFixed(1)} ${r.toFixed(1)},${i.toFixed(1)}`,w=vY(p,y,c,.52),T=yY(p,y,c,.52),E=vY(p,b,c,.48),D=yY(p,b,c,.48)+180,O=t[e.name]??0;return(0,J.jsxs)(`g`,{children:[(0,J.jsx)(`path`,{id:`conn-cmd-${n}`,className:`conn-link`,d:x,stroke:o?bY:a.color,strokeOpacity:o?.5:.32,strokeDasharray:s?`2 8`:o?void 0:`6 7`}),(0,J.jsx)(`path`,{id:`conn-res-${n}`,className:`conn-link`,d:S,stroke:o?xY:a.color,strokeOpacity:o?.5:.18,strokeDasharray:o?void 0:`6 7`}),o&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`path`,{className:`conn-flow`,d:x,stroke:`#e4e4e7`}),(0,J.jsx)(`circle`,{className:`conn-packet`,r:3.2,fill:`#e4e4e7`,children:(0,J.jsx)(`animateMotion`,{dur:`2.6s`,repeatCount:`indefinite`,calcMode:`linear`,children:(0,J.jsx)(`mpath`,{href:`#conn-cmd-${n}`})})}),(0,J.jsx)(`rect`,{className:`conn-packet`,x:-2.6,y:-2.6,width:5.2,height:5.2,fill:`#d4d4d8`,transform:`rotate(45)`,children:(0,J.jsx)(`animateMotion`,{dur:`2.6s`,begin:`0.9s`,repeatCount:`indefinite`,calcMode:`linear`,keyPoints:`1;0`,keyTimes:`0;1`,children:(0,J.jsx)(`mpath`,{href:`#conn-res-${n}`})})}),(0,J.jsx)(`path`,{className:`conn-chevron`,d:`M-4,-3 L4,0 L-4,3`,stroke:`#e4e4e7`,transform:`translate(${w.x.toFixed(1)},${w.y.toFixed(1)}) rotate(${T.toFixed(1)})`}),(0,J.jsx)(`path`,{className:`conn-chevron`,d:`M-4,-3 L4,0 L-4,3`,stroke:`#d4d4d8`,transform:`translate(${E.x.toFixed(1)},${E.y.toFixed(1)}) rotate(${D.toFixed(1)})`})]}),O>0&&(0,J.jsx)(`g`,{children:(0,J.jsxs)(`circle`,{r:6,fill:`url(#conn-burst)`,children:[(0,J.jsx)(`animateMotion`,{dur:`1.1s`,repeatCount:`1`,calcMode:`linear`,keyPoints:`0;1;0`,keyTimes:`0;0.5;1`,path:C}),(0,J.jsx)(`animate`,{attributeName:`opacity`,values:`0;1;1;0`,keyTimes:`0;0.1;0.85;1`,dur:`1.1s`,repeatCount:`1`,fill:`freeze`})]})},`burst-${e.name}-${O}`)]},`l-${e.name}`)}),m.map(e=>{let t=e.d.jumpVia,n=t?m.find(e=>e.d.name===t):void 0,r=!n&&e.d.jumpHost?e.d.jumpHost:void 0;if(!n&&!r)return null;let i={x:e.x,y:e.y},a=Math.hypot(e.x-d,e.y-f)||1,o=(e.x-d)/a,s=(e.y-f)/a,c=n?{x:n.x,y:n.y}:{x:e.x+o*(e.r+46),y:e.y+s*(e.r+46)},l=(c.x+i.x)/2,u=(c.y+i.y)/2,p=Math.hypot(l-d,u-f)||1,h=n?50:16,g={x:l+(l-d)/p*h,y:u+(u-f)/p*h},_=`M${c.x.toFixed(1)},${c.y.toFixed(1)} Q${g.x.toFixed(1)},${g.y.toFixed(1)} ${i.x.toFixed(1)},${i.y.toFixed(1)}`,v=CY(e.d)??``,y=vY(c,g,i,.5),b=`conn-tunnel-${e.i}`;return(0,J.jsxs)(`g`,{className:`conn-tunnel-g`,children:[(0,J.jsx)(`path`,{className:`conn-tunnel-halo`,d:_,stroke:SY}),(0,J.jsx)(`path`,{id:b,className:`conn-tunnel`,d:_,stroke:`url(#conn-tunnel-grad)`,filter:`url(#conn-tunnel-glow)`}),r&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`circle`,{className:`conn-tunnel-sat`,cx:c.x,cy:c.y,r:11,fill:`#1c1917`,stroke:`#f59e0b`}),(0,J.jsx)(`text`,{x:c.x,y:c.y+3.5,textAnchor:`middle`,fontSize:11,children:`🛡️`})]}),(0,J.jsxs)(`text`,{className:`conn-tunnel-lock`,fontSize:13,textAnchor:`middle`,children:[`🔒`,(0,J.jsx)(`animateMotion`,{dur:`2.4s`,repeatCount:`indefinite`,calcMode:`linear`,rotate:`auto`,children:(0,J.jsx)(`mpath`,{href:`#${b}`})})]}),(0,J.jsx)(hY,{x:y.x,y:y.y,text:`⤳ via ${v}`,className:`conn-tunnel-badge`,textClassName:`conn-tunnel-badge-tx`})]},`jump-${e.d.name}`)}),(0,J.jsx)(`circle`,{className:`conn-hub-glow`,cx:d,cy:f,r:42}),(0,J.jsx)(`circle`,{className:`conn-hub-ring`,cx:d,cy:f,r:37}),(0,J.jsx)(`circle`,{cx:d,cy:f,r:29,fill:`url(#conn-hub)`,stroke:`#71717a`,strokeWidth:1.5}),(0,J.jsx)(`text`,{x:d,y:f-4,textAnchor:`middle`,fill:`#09090b`,fontSize:12,fontWeight:700,children:`LLM`}),(0,J.jsx)(`text`,{x:d,y:f+8,textAnchor:`middle`,fill:`#3f3f46`,fontSize:8,fontWeight:600,children:`⇄ MCP`}),(0,J.jsx)(`text`,{x:d,y:f+18,textAnchor:`middle`,fill:`#3f3f46`,fontSize:7.5,fontWeight:600,children:`server`}),m.map(({d:e,x:t,y:n,r,label:i})=>{let a=gY(e.status),o=e.status.reachable===!0,s=o?`${e.status.latencyMs??`?`} ms`:a.label,c=_Y(e.name);return(0,J.jsxs)(`g`,{className:`conn-node`,children:[o&&(0,J.jsx)(`circle`,{className:`conn-node-halo`,cx:t,cy:n,r:r+1,stroke:c}),(0,J.jsx)(`circle`,{cx:t,cy:n,r,fill:`url(#conn-orb)`}),(0,J.jsx)(`circle`,{cx:t,cy:n,r,fill:c,opacity:.16}),(0,J.jsx)(`circle`,{cx:t,cy:n,r,fill:`none`,stroke:c,strokeWidth:2.5}),(0,J.jsx)(`circle`,{className:o?`conn-blink`:void 0,cx:t+r*.7,cy:n-r*.7,r:4.5,fill:a.color,stroke:`#0a121b`,strokeWidth:1.5}),(0,J.jsx)(`text`,{x:t,y:n+3.5,textAnchor:`middle`,fill:`#fafafa`,fontSize:10,fontWeight:600,children:i}),(0,J.jsx)(`text`,{x:t,y:n+r+14,textAnchor:`middle`,fill:`#a1a1aa`,fontSize:9,children:e.address??e.host}),(0,J.jsx)(`text`,{x:t,y:n+r+26,textAnchor:`middle`,fill:a.color,fontSize:9,fontWeight:600,children:s})]},`n-${e.name}`)})]}),(0,J.jsxs)(`div`,{className:`conn-legend`,children:[(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:bY}}),` command · LLM → device`]}),(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:xY}}),` response · device → LLM`]}),(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:`#e4e4e7`}}),` live call (round-trip)`]}),e.devices.some(e=>CY(e))&&(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{style:{background:`#f59e0b`}}),` 🔒 SSH jump tunnel (ProxyJump)`]})]})]})}function OY({d:e}){let t=gY(e.status),n=e.status.reachable===!0?`${t.label} · ${e.status.latencyMs??`?`}ms${e.status.version?` · v${e.status.version}`:``}`:e.status.reachable===!1?`${t.label}${e.status.error?` · ${e.status.error}`:``}`:t.label,r=_Y(e.name);return(0,J.jsxs)(`div`,{className:`card dev-card`,style:{borderLeft:`3px solid ${r}`},children:[(0,J.jsxs)(`div`,{className:`dev-card__top`,children:[(0,J.jsx)(`span`,{className:`dot`,style:{background:r},title:`device colour`}),(0,J.jsx)(`span`,{className:`dev-card__name`,children:e.name}),e.isDefault&&(0,J.jsx)(Mc,{type:`accent`,children:`default`}),(0,J.jsx)(`span`,{className:`dot dot--status`,style:{background:t.color},title:t.label}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`span`,{className:`chip`,children:e.authMode})]}),(0,J.jsxs)(`div`,{className:`dev-card__meta`,children:[(0,J.jsx)(`span`,{children:e.mac?`mac`:`host`}),(0,J.jsx)(`b`,{children:e.address??`${e.host}:${e.port}`}),(0,J.jsx)(`span`,{children:`user`}),(0,J.jsx)(`b`,{children:e.username}),(0,J.jsx)(`span`,{children:`status`}),(0,J.jsx)(`b`,{style:{color:t.color},children:n}),(0,J.jsx)(`span`,{children:`activity`}),(0,J.jsxs)(`b`,{children:[e.activity.calls,` calls · `,e.activity.errors,` err`,e.activity.avgMs?` · ${xc(e.activity.avgMs)} avg`:``]}),e.description&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`span`,{children:`note`}),(0,J.jsx)(`b`,{children:e.description})]})]}),CY(e)&&(0,J.jsxs)(`div`,{className:`jump-route`,title:`Reached over SSH through the bastion ${CY(e)} (ProxyJump) — no port exposed on ${e.name}.`,children:[(0,J.jsxs)(`span`,{className:`jump-route__hop jump-route__hop--bastion`,children:[`🛡️ `,CY(e),e.jumpVia?(0,J.jsx)(`i`,{className:`jump-route__tag`,children:`jump`}):null]}),(0,J.jsx)(`span`,{className:`jump-route__wire jump-route__wire--enc`,children:(0,J.jsx)(`span`,{className:`jump-route__lock`,"aria-hidden":!0,children:`🔒`})}),(0,J.jsxs)(`span`,{className:`jump-route__hop jump-route__hop--dest`,style:{borderColor:r},title:e.name,children:[`📡 `,e.name]})]})]})}function kY({event:e,onClose:t}){let[n,r]=(0,v.useState)(sY);return(0,J.jsx)(`div`,{className:`overlay`,onClick:e=>e.target===e.currentTarget&&t(),children:(0,J.jsxs)(`div`,{className:`sheet`,children:[(0,J.jsxs)(`div`,{className:`sheet__hd`,children:[(0,J.jsx)(`span`,{className:`risk risk-${e.risk}`,children:e.risk}),(0,J.jsxs)(`h3`,{className:`sheet__tool`,children:[e.tool,(0,J.jsx)(bc,{text:e.tool,className:`iconbtn`,icon:!0,title:`Copy tool name`})]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(Ac,{type:`secondary`,size:`sm`,onClick:t,children:`✕ Close`})]}),(0,J.jsxs)(`div`,{className:`kv__body`,children:[(0,J.jsx)(`div`,{className:`kv__k`,children:`title`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.title}),(0,J.jsx)(`div`,{className:`kv__k`,children:`time`}),(0,J.jsx)(`div`,{className:`kv__v`,children:new Date(e.ts).toLocaleString(void 0,{hour12:!1})}),(0,J.jsx)(`div`,{className:`kv__k`,children:`device`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.device??`—`}),(0,J.jsx)(`div`,{className:`kv__k`,children:`transport`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.transport??`—`}),(0,J.jsx)(`div`,{className:`kv__k`,children:`duration`}),(0,J.jsx)(`div`,{className:`kv__v`,children:xc(e.durationMs)}),(0,J.jsx)(`div`,{className:`kv__k`,children:`status`}),(0,J.jsx)(`div`,{className:`kv__v`,children:(0,J.jsx)(`span`,{className:e.isError?`status-err`:`status-ok`,children:e.isError?`error`:`ok`})}),(0,J.jsx)(`div`,{className:`kv__k`,children:`output size`}),(0,J.jsxs)(`div`,{className:`kv__v`,children:[Sc(e.outputBytes),e.truncated?` (truncated)`:``]}),(0,J.jsx)(`div`,{className:`kv__k`,children:`structured`}),(0,J.jsx)(`div`,{className:`kv__v`,children:e.hasStructured?`yes (renders an MCP App view)`:`no`})]}),e.error&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`h2`,{className:`muted`,children:`ERROR`}),(0,J.jsx)(`pre`,{className:`body`,style:{color:`var(--mt-bad)`},children:e.error})]}),(0,J.jsxs)(`div`,{className:`sheet__hd`,children:[(0,J.jsx)(`h2`,{className:`muted`,style:{margin:0},children:`INPUT`}),(0,J.jsx)(`span`,{style:{flex:1}}),e.input&&(0,J.jsx)(Ac,{type:`secondary`,size:`sm`,ghost:!0,onClick:()=>r(e=>{let t=!e;return cY(t),t}),title:n?`Showing pretty-printed JSON — click for raw`:`Showing raw JSON — click to pretty-print`,children:n?`✦ Pretty`:`{ } Raw`}),(0,J.jsx)(bc,{text:e.input,title:`Copy input JSON`})]}),e.input?(0,J.jsx)(uY,{value:lY(e.input,n)}):(0,J.jsx)(`pre`,{className:`body`,children:`—`}),(0,J.jsxs)(`div`,{className:`sheet__hd`,children:[(0,J.jsx)(`h2`,{className:`muted`,style:{margin:0},children:`OUTPUT`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(bc,{text:e.output,title:`Copy output`})]}),(0,J.jsx)(`pre`,{className:`body ros`,children:e.output?aY(e.output):`—`})]})})}var AY=e=>e==null?`?`:Sc(e);function jY({d:e}){let t=e.status,n=e.history??[];return t.reachable===!0||n.length>0?(0,J.jsxs)(`div`,{className:`card health-card`,children:[(0,J.jsxs)(`div`,{className:`health-card__hd`,children:[(0,J.jsx)(Nc,{color:gY(t).color}),(0,J.jsx)(`span`,{className:`dev-card__name`,children:e.name}),e.isDefault&&(0,J.jsx)(Mc,{type:`accent`,children:`default`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(Mc,{type:t.version?`success`:`default`,children:t.version?`v${t.version}`:`—`})]}),(0,J.jsxs)(`div`,{className:`health-card__sub muted`,children:[t.boardName??`router`,t.architecture?` · ${t.architecture}`:``,t.cpuCount?` · ${t.cpuCount} cpu`:``,t.uptime?` · up ${t.uptime}`:``]}),(0,J.jsxs)(`div`,{className:`health-card__gauges`,children:[(0,J.jsx)(HJ,{value:t.cpuLoad,label:`CPU`,color:Ec.cpu}),(0,J.jsx)(HJ,{value:t.memUsedPct,label:`MEM`,color:Ec.mem}),(0,J.jsx)(HJ,{value:t.hddUsedPct,label:`DISK`,color:Ec.disk})]}),(0,J.jsxs)(`div`,{className:`health-card__charts`,children:[(0,J.jsxs)(`div`,{className:`health-chart`,children:[(0,J.jsx)(`span`,{className:`health-chart__k`,children:`CPU load`}),(0,J.jsx)(VJ,{id:`${e.name}-cpu`,values:n.map(e=>e.cpuLoad),color:Ec.cpu,maxValue:100,unit:`%`})]}),(0,J.jsxs)(`div`,{className:`health-chart`,children:[(0,J.jsx)(`span`,{className:`health-chart__k`,children:`Memory used`}),(0,J.jsx)(VJ,{id:`${e.name}-mem`,values:n.map(e=>e.memUsedPct),color:Ec.mem,maxValue:100,unit:`%`})]}),(0,J.jsxs)(`div`,{className:`health-chart`,children:[(0,J.jsx)(`span`,{className:`health-chart__k`,children:`Probe latency`}),(0,J.jsx)(VJ,{id:`${e.name}-lat`,values:n.map(e=>e.latencyMs),color:Ec.latency,unit:`ms`})]})]}),(0,J.jsxs)(`div`,{className:`health-card__foot muted`,children:[`RAM `,AY(t.totalMemory&&t.freeMemory?t.totalMemory-t.freeMemory:void 0),` /`,` `,AY(t.totalMemory),` · free disk `,AY(t.freeHdd)]})]}):(0,J.jsxs)(`div`,{className:`card health-card health-card--na`,children:[(0,J.jsxs)(`div`,{className:`health-card__hd`,children:[(0,J.jsx)(Nc,{color:gY(t).color}),(0,J.jsx)(`span`,{className:`dev-card__name`,children:e.name}),e.isDefault&&(0,J.jsx)(Mc,{type:`accent`,children:`default`})]}),(0,J.jsx)(`p`,{className:`muted`,style:{margin:0},children:t.reachable===!1?`Offline — ${t.error??`unreachable`}`:e.mac?`Waiting for the first MAC-Telnet probe (these run every few minutes to avoid contending with tool calls)…`:`Waiting for the first health probe…`})]})}function MY(e,t){let n=(0,v.useRef)(e),r=(0,v.useRef)(t);n.current=e,r.current=t,(0,v.useEffect)(()=>{let e=!1,t=null,i=null,a=()=>{e||(i=new EventSource(dc(`/api/sse`)),i.addEventListener(`hello`,()=>r.current(`sse`)),i.addEventListener(`tool`,e=>{try{n.current(JSON.parse(e.data))}catch{}}),i.onerror=()=>{i&&i.readyState===EventSource.CONNECTING&&r.current(`off`)})},o=()=>{if(e)return;let i=location.protocol===`https:`?`wss`:`ws`;t=new WebSocket(dc(`${i}://${location.host}/api/stream`));let s=!1;t.onopen=()=>{s=!0,r.current(`ws`)},t.onerror=()=>t?.close(),t.onmessage=e=>{try{let t=JSON.parse(e.data);t.type===`event`&&t.event&&n.current(t.event)}catch{}},t.onclose=()=>{e||(r.current(`off`),s?setTimeout(o,2e3):a())}};return o(),()=>{e=!0,t?.close(),i?.close()}},[])}function NY(e){(0,v.useLayoutEffect)(()=>{let t=e.current;if(!t||window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;document.documentElement.classList.add(`js-motion`);let n=new WeakSet,r=e=>{n.has(e)||(n.add(e),Ri.set(e,{opacity:0,y:26}),q.create({trigger:e,start:`top 90%`,once:!0,onEnter:()=>Ri.to(e,{opacity:1,y:0,duration:.7,ease:`power3.out`})}))},i=e=>{for(let t of e.querySelectorAll(`.reveal`))r(t)};i(t);let a=new MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e instanceof Element&&(e.matches(`.reveal`)&&r(e),i(e))});a.observe(t,{childList:!0,subtree:!0});let o=setInterval(()=>q.refresh(),1200),s=setTimeout(()=>clearInterval(o),7e3);return()=>{a.disconnect(),clearInterval(o),clearTimeout(s);for(let e of q.getAll())e.kill();document.documentElement.classList.remove(`js-motion`)}},[e])}function PY({m:e,busy:t,onToggle:n}){return(0,J.jsxs)(`label`,{className:`mod-row`,"data-on":e.enabled?`1`:void 0,title:e.description,children:[(0,J.jsx)(`input`,{type:`checkbox`,checked:e.enabled,disabled:t,onChange:()=>n(e.slug,!e.enabled)}),(0,J.jsxs)(`span`,{className:`mod-row__main`,children:[(0,J.jsxs)(`span`,{className:`mod-row__name`,children:[e.label,(0,J.jsx)(`code`,{className:`mod-row__slug`,children:e.slug})]}),(0,J.jsx)(`span`,{className:`mod-row__desc muted`,children:e.description})]}),(0,J.jsxs)(`span`,{className:`mod-row__count muted`,children:[e.toolCount,` tool`,e.toolCount===1?``:`s`]})]})}function FY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(new Set),[i,a]=(0,v.useState)(null),[o,s]=(0,v.useState)(``),c=(0,v.useCallback)(()=>{fc(`/api/modules`).then(t).catch(()=>a(`could not load modules`))},[]);(0,v.useEffect)(()=>c(),[c]);let l=e=>t(t=>t?{...t,...e}:e),u=(0,v.useCallback)(async(e,n)=>{r(t=>new Set(t).add(e)),t(t=>t&&{...t,modules:t.modules.map(t=>t.slug===e?{...t,enabled:n}:t)});let i=await pc(`/api/modules/toggle`,{slug:e,enabled:n}).catch(()=>({error:`request failed`}));if(r(t=>{let n=new Set(t);return n.delete(e),n}),i.error||i.ok===!1){a(i.error??`toggle failed`),c();return}l(i);let o=i.persisted?`saved to config`:`applied live (not saved)`,s=i.warning?` ⚠ ${i.warning}`:``;a(`${e} ${n?`enabled`:`disabled`} — ${o}. Reconnect the MCP client (or restart the server) for the tool list to update.${s}`)},[c]),d=(0,v.useCallback)(async(e,t)=>{for(let n of e)await u(n,t)},[u]),f=(0,v.useMemo)(()=>{if(!e)return[];let t=o.trim().toLowerCase(),n=e=>!t||e.slug.toLowerCase().includes(t)||e.label.toLowerCase().includes(t)||e.group.toLowerCase().includes(t)||e.description.toLowerCase().includes(t),r=new Map;for(let t of e.modules){if(!n(t))continue;let e=r.get(t.group)??[];e.push(t),r.set(t.group,e)}return[...r.entries()].map(([e,t])=>({group:e,modules:t}))},[e,o]);if(!e)return(0,J.jsx)(`div`,{className:`muted`,children:`loading modules…`});let p=f.reduce((e,t)=>e+t.modules.length,0);return(0,J.jsx)(`section`,{className:`view`,children:(0,J.jsxs)(gc,{title:`Tool modules`,className:`reveal`,extra:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`span`,{className:`muted`,children:[e.enabledModules,`/`,e.total,` modules · `,e.enabledTools,`/`,e.totalTools,` `,`tools exposed`]}),(0,J.jsx)(`button`,{className:`btn`,onClick:c,style:{marginLeft:10},children:`↻ Refresh`})]}),children:[(0,J.jsxs)(`div`,{className:`legend`,style:{margin:`0 0 10px`},children:[(0,J.jsxs)(`span`,{children:[`writes to: `,(0,J.jsx)(`code`,{children:e.source?.path??`config file`})]}),(0,J.jsx)(`span`,{children:e.hasAllowList?`allow-list active`:`all modules on by default`})]}),(0,J.jsxs)(`p`,{className:`muted`,style:{margin:`0 0 12px`,fontSize:12},children:[`Toggle a module to expose or hide all of its tools. Disabling adds it to`,` `,(0,J.jsx)(`code`,{children:`tools.disabledModules`}),` in your config file; enabling removes it (or adds it to`,` `,(0,J.jsx)(`code`,{children:`tools.enabledModules`}),` when an allow-list is in force). Trimming the surface below ~150–200 tools makes every remaining tool reliably findable by the MCP client. The client must reconnect for changes to take effect.`]}),(0,J.jsxs)(`div`,{className:`mod-toolbar`,children:[(0,J.jsx)(`input`,{className:`input`,placeholder:`Filter modules by name, slug, group or description…`,value:o,onChange:e=>s(e.target.value)}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>void d(e.modules.filter(e=>!e.enabled).map(e=>e.slug),!0),children:`Enable all`}),` `,(0,J.jsx)(`button`,{className:`btn`,onClick:()=>void d(e.modules.filter(e=>e.enabled).map(e=>e.slug),!1),children:`Disable all`})]}),i&&(0,J.jsx)(`div`,{className:`cfg-msg`,style:{marginTop:12},children:i}),p===0?(0,J.jsxs)(`div`,{className:`muted`,style:{padding:12},children:[`No modules match “`,o,`”.`]}):(0,J.jsx)(`div`,{className:`mod-groups`,children:f.map(({group:e,modules:t})=>{let r=t.filter(e=>e.enabled).length,i=t.map(e=>e.slug);return(0,J.jsxs)(`div`,{className:`mod-group`,children:[(0,J.jsxs)(`div`,{className:`mod-group__hd`,children:[(0,J.jsx)(`h3`,{className:`mod-group__title`,children:e}),(0,J.jsxs)(`span`,{className:`muted`,children:[r,`/`,t.length,` on`]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`btn btn-sm`,onClick:()=>void d(i.filter(e=>{let n=t.find(t=>t.slug===e);return n?!n.enabled:!1}),!0),children:`Enable group`}),` `,(0,J.jsx)(`button`,{className:`btn btn-sm`,onClick:()=>void d(i.filter(e=>{let n=t.find(t=>t.slug===e);return n?n.enabled:!1}),!1),children:`Disable group`})]}),(0,J.jsx)(`div`,{className:`mod-list`,children:t.map(e=>(0,J.jsx)(PY,{m:e,busy:n.has(e.slug),onToggle:(e,t)=>void u(e,t)},e.slug))})]},e)})})]})})}var IY={TCP:`#e4e4e7`,UDP:`#d4d4d8`,ICMP:`#a1a1aa`,ICMPv6:`#a1a1aa`,ARP:`#e4e4e7`,IPv6:`#a1a1aa`},LY=e=>e&&IY[e]||`#71717a`;function RY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(!1);(0,v.useEffect)(()=>{let e=()=>void fc(`/api/capture/packets?limit=150`).then(t).catch(()=>{});e();let n=setInterval(e,1500);return()=>clearInterval(n)},[]);let i=e?.stats,a=e?.packets??[],o=async()=>{r(!0),await pc(`/api/capture/stop`,{}).catch(()=>{}),r(!1)};if(!i||!i.running&&i.packets===0)return(0,J.jsxs)(`div`,{className:`cap-idle muted`,children:[`No capture running. Start one with the `,(0,J.jsx)(`code`,{children:`start_packet_capture`}),` tool — point the device's TZSP stream at this host — and decoded packets stream in here live.`]});let s=Math.max(1,...Object.values(i.protocols));return(0,J.jsxs)(`div`,{className:`cap`,children:[(0,J.jsxs)(`div`,{className:`cap-bar`,children:[(0,J.jsx)(`span`,{className:`cap-dot${i.running?` is-on`:``}`}),(0,J.jsx)(`b`,{children:i.running?`capturing`:`stopped`}),(0,J.jsxs)(`span`,{className:`muted`,children:[`UDP `,i.port]}),(0,J.jsxs)(`span`,{className:`muted`,children:[wc(i.packets),` pkts · `,Sc(i.bytes)]}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`a`,{className:`btn`,href:dc(`/api/capture/pcap`),download:`capture.pcap`,children:`⤓ pcap`}),(0,J.jsx)(`button`,{className:`btn btn-danger`,onClick:()=>void o(),disabled:n||!i.running,children:`■ Stop`})]}),(0,J.jsxs)(`div`,{className:`cap-cols`,children:[(0,J.jsxs)(`div`,{className:`cap-side`,children:[(0,J.jsx)(`div`,{className:`cap-h`,children:`Protocols`}),Object.entries(i.protocols).map(([e,t])=>(0,J.jsxs)(`div`,{className:`cap-pbar`,children:[(0,J.jsx)(`span`,{className:`cap-plabel`,style:{color:LY(e)},children:e}),(0,J.jsx)(`span`,{className:`cap-ptrack`,children:(0,J.jsx)(`i`,{style:{width:`${t/s*100}%`,background:LY(e)}})}),(0,J.jsx)(`span`,{className:`cap-pn`,children:t})]},e)),(0,J.jsx)(`div`,{className:`cap-h`,style:{marginTop:12},children:`Top talkers`}),i.topTalkers.length===0&&(0,J.jsx)(`div`,{className:`muted`,children:`—`}),i.topTalkers.map(e=>(0,J.jsxs)(`div`,{className:`cap-talker`,children:[(0,J.jsx)(`span`,{children:e.addr}),(0,J.jsx)(`b`,{children:e.count})]},e.addr))]}),(0,J.jsx)(`div`,{className:`cap-list`,children:a.length===0?(0,J.jsx)(`div`,{className:`muted`,style:{padding:10},children:`waiting for packets…`}):a.map((e,t)=>(0,J.jsxs)(`div`,{className:`cap-row`,children:[(0,J.jsx)(`span`,{className:`cap-tt`,children:Cc(e.ts)}),(0,J.jsx)(`span`,{className:`cap-proto`,style:{color:LY(e.protocol)},children:e.protocol??e.ethType}),(0,J.jsx)(`span`,{className:`cap-len`,children:e.len}),(0,J.jsx)(`span`,{className:`cap-info`,children:e.info})]},t))})]})]})}function zY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(null),[i,a]=(0,v.useState)(null),[o,s]=(0,v.useState)(null),c=(0,v.useCallback)(()=>{fc(`/api/s3/list`).then(t).catch(()=>t({configured:!1,objects:[]}))},[]);(0,v.useEffect)(()=>c(),[c]);let l=e=>{fc(`/api/s3/presign?key=${encodeURIComponent(e)}`).then(e=>{e.url?window.open(e.url,`_blank`,`noopener`):s(`could not generate download link`)}).catch(()=>s(`could not generate download link`))},u=async e=>{a(e);let t=await pc(`/api/s3/delete`,{key:e}).catch(()=>({error:`request failed`}));a(null),r(null),t.ok?(s(`Deleted ${e}`),c()):s(t.error??`delete failed`)};return e?e.configured?(0,J.jsx)(`section`,{className:`view`,children:(0,J.jsxs)(gc,{title:`S3 backups`,className:`reveal`,extra:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`span`,{className:`muted`,children:[e.target,` · `,e.objects.length,` object`,e.objects.length===1?``:`s`,e.truncated?` (truncated)`:``]}),(0,J.jsx)(`button`,{className:`btn`,onClick:c,style:{marginLeft:10},children:`↻ Refresh`})]}),children:[o&&(0,J.jsx)(`div`,{className:`cfg-msg`,children:o}),e.objects.length===0?(0,J.jsxs)(`div`,{className:`muted`,style:{padding:12},children:[`No objects in the bucket. Upload one with the `,(0,J.jsx)(`code`,{children:`upload_backup_to_s3`}),` tool.`]}):(0,J.jsx)(`div`,{className:`feedwrap`,children:(0,J.jsxs)(`table`,{className:`feed`,children:[(0,J.jsx)(`thead`,{children:(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`th`,{children:`key`}),(0,J.jsx)(`th`,{className:`num`,children:`size`}),(0,J.jsx)(`th`,{children:`modified`}),(0,J.jsx)(`th`,{style:{width:200},children:`actions`})]})}),(0,J.jsx)(`tbody`,{children:e.objects.map(e=>(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`td`,{children:e.key}),(0,J.jsx)(`td`,{className:`num`,children:Sc(e.size)}),(0,J.jsx)(`td`,{children:e.lastModified?new Date(e.lastModified).toLocaleString(void 0,{hour12:!1}):`—`}),(0,J.jsxs)(`td`,{onClick:e=>e.stopPropagation(),children:[(0,J.jsx)(`button`,{className:`btn`,onClick:()=>l(e.key),children:`⤓ Download`}),` `,n===e.key?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`button`,{className:`btn btn-danger`,disabled:i===e.key,onClick:()=>void u(e.key),children:`✓ Confirm`}),` `,(0,J.jsx)(`button`,{className:`btn`,onClick:()=>r(null),children:`Cancel`})]}):(0,J.jsx)(`button`,{className:`btn`,onClick:()=>r(e.key),children:`🗑 Delete`})]})]},e.key))})]})})]})}):(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`☁️`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`S3 is not configured`}),(0,J.jsxs)(`p`,{className:`feed-empty__sub`,children:[`Add an `,(0,J.jsx)(`code`,{children:`s3`}),` block (bucket + credentials) to your config to manage backup objects here.`]})]}):(0,J.jsx)(`div`,{className:`muted`,children:`loading S3 objects…`})}function BY(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(``),[i,a]=(0,v.useState)(``),[o,s]=(0,v.useState)(null),[c,l]=(0,v.useState)(null),u=(0,v.useCallback)(()=>{fc(`/api/snapshots`).then(e=>t(e.snapshots)).catch(()=>t([]))},[]);(0,v.useEffect)(()=>u(),[u]);let d=e=>{fc(`/api/snapshot/${encodeURIComponent(e)}`).then(s).catch(()=>{})},f=()=>{!n||!i||pc(`/api/snapshots/diff`,{from:n,to:i}).then(l).catch(()=>{})};if(!e)return(0,J.jsx)(`div`,{className:`muted`,children:`loading snapshots…`});if(e.length===0)return(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`🕰️`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`No config snapshots yet`}),(0,J.jsxs)(`p`,{className:`feed-empty__sub`,children:[`Capture one with the `,(0,J.jsx)(`code`,{children:`capture_config_snapshot`}),` tool — then time-travel diff any two here.`]})]});let p=e.map(e=>(0,J.jsxs)(`option`,{value:e.id,children:[e.device,` · `,e.label??e.id,` · `,Cc(e.ts)]},e.id));return(0,J.jsxs)(`section`,{className:`view`,children:[(0,J.jsxs)(gc,{title:`Config snapshots`,className:`reveal`,extra:(0,J.jsx)(`button`,{className:`btn`,onClick:u,children:`↻ Refresh`}),children:[(0,J.jsxs)(`div`,{className:`toolbar`,style:{marginBottom:12},children:[(0,J.jsxs)(`select`,{className:`btn`,value:n,onChange:e=>r(e.target.value),children:[(0,J.jsx)(`option`,{value:``,children:`diff from…`}),p]}),(0,J.jsxs)(`select`,{className:`btn`,value:i,onChange:e=>a(e.target.value),children:[(0,J.jsx)(`option`,{value:``,children:`to…`}),p]}),(0,J.jsx)(`button`,{className:`btn is-active`,onClick:f,disabled:!n||!i,children:`Diff →`}),(0,J.jsxs)(`span`,{className:`muted`,children:[e.length,` snapshots`]})]}),(0,J.jsx)(`div`,{className:`feedwrap`,children:(0,J.jsxs)(`table`,{className:`feed`,children:[(0,J.jsx)(`thead`,{children:(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`th`,{children:`captured`}),(0,J.jsx)(`th`,{children:`device`}),(0,J.jsx)(`th`,{children:`label`}),(0,J.jsx)(`th`,{children:`version`}),(0,J.jsx)(`th`,{className:`num`,children:`lines`}),(0,J.jsx)(`th`,{className:`num`,children:`size`}),(0,J.jsx)(`th`,{children:`output`})]})}),(0,J.jsx)(`tbody`,{children:e.map(e=>(0,J.jsxs)(`tr`,{className:o?.id===e.id?`is-selected`:void 0,onClick:()=>d(e.id),children:[(0,J.jsx)(`td`,{children:Cc(e.ts)}),(0,J.jsx)(`td`,{children:e.device}),(0,J.jsx)(`td`,{children:e.label??`—`}),(0,J.jsx)(`td`,{children:e.rosVersion??`—`}),(0,J.jsx)(`td`,{className:`num`,children:e.lines}),(0,J.jsx)(`td`,{className:`num`,children:Sc(e.bytes)}),(0,J.jsx)(`td`,{className:`preview`,children:`view export →`})]},e.id))})]})})]}),c&&(0,J.jsx)(gc,{title:`Time-travel diff`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:c.summary.changed?`+${c.summary.added} / -${c.summary.removed}`:`identical`}),children:(0,J.jsx)(`pre`,{className:`cfg-diff`,children:(c.unified||`(identical)`).split(`
80
80
  `).map((e,t)=>(0,J.jsx)(`div`,{className:e.startsWith(`+`)?`d-add`:e.startsWith(`-`)?`d-del`:e.startsWith(`@@`)?`d-hunk`:``,children:e||` `},t))})}),o&&(0,J.jsx)(gc,{title:`Snapshot · ${o.label??o.id}`,className:`reveal`,extra:(0,J.jsx)(Ac,{type:`secondary`,size:`sm`,onClick:()=>s(null),children:`✕ Close`}),children:(0,J.jsx)(`pre`,{className:`body`,style:{maxHeight:460},children:o.body||`(empty)`})})]})}function VY(e){return e==null?`#3f3f46`:e>=85?`#f87171`:e>=60?`#a1a1aa`:`#d4d4d8`}function HY({topo:e,onOnboard:t}){let[n,r]=(0,v.useState)(null),i=e.nodes.filter(e=>e.kind===`device`),a=e.nodes.filter(e=>e.kind===`neighbor`),o=Math.max(1,i.length),s=i.length<=1?0:Math.min(150,76+o*8),c=s+(i.length<=1?180:140),l=Math.max(360,Math.round((c+78)*2)),u=l/2,d=new Map,f=new Map;i.forEach((e,t)=>{if(i.length===1){d.set(e.id,{x:380,y:u}),f.set(e.id,-Math.PI/2);return}let n=t/o*Math.PI*2-Math.PI/2;f.set(e.id,n),d.set(e.id,{x:380+s*Math.cos(n),y:u+s*Math.sin(n)})});let p=new Map,m=new Set(a.map(e=>e.id));for(let t of e.edges)m.has(t.to)&&!p.has(t.to)&&p.set(t.to,t.from);let h=new Map;for(let e of a){let t=p.get(e.id)??i[0]?.id??``;h.set(t,[...h.get(t)??[],e.id])}if(i.length<=1)a.forEach((e,t)=>{let n=t/Math.max(1,a.length)*Math.PI*2-Math.PI/2;d.set(e.id,{x:380+c*Math.cos(n),y:u+c*Math.sin(n)})});else for(let[e,t]of h){let n=f.get(e)??-Math.PI/2,r=Math.min(Math.PI/2.2,.32*t.length);t.forEach((e,i)=>{let a=t.length===1?n:n-r/2+r*i/(t.length-1);d.set(e,{x:380+c*Math.cos(a),y:u+c*Math.sin(a)})})}let g=new Map(e.nodes.map(e=>[e.id,e])),_=n?g.get(n):null;return(0,J.jsxs)(`div`,{className:`topo`,children:[(0,J.jsxs)(`svg`,{viewBox:`0 0 760 ${l}`,width:`100%`,height:Math.min(l,540),preserveAspectRatio:`xMidYMid meet`,children:[e.edges.map((e,t)=>{let n=d.get(e.from),r=d.get(e.to);return!n||!r?null:(0,J.jsx)(`line`,{className:`topo-edge${m.has(e.to)?` is-dashed`:``}`,x1:n.x,y1:n.y,x2:r.x,y2:r.y,children:(0,J.jsxs)(`title`,{children:[e.from,` → `,e.to,e.interface?` (${e.interface})`:``]})},`e-${t}`)}),e.nodes.map(e=>{let t=d.get(e.id);if(!t)return null;let i=e.kind===`device`,a=i?132:108,o=i?50:38,s=t.x-a/2,c=t.y-o/2,l=e.reachable===!0?`#d4d4d8`:e.reachable===!1?`#f87171`:i?`#71717a`:`#e4e4e7`,u=n===e.id;return(0,J.jsxs)(`g`,{className:`topo-node${i?` is-device`:` is-neighbor`}${e.onboardable?` is-onboard`:``}${u?` is-picked`:``}`,transform:`translate(${s},${c})`,onClick:()=>r(t=>t===e.id?null:e.id),children:[(0,J.jsx)(`rect`,{width:a,height:o,rx:9,style:{stroke:l}}),(0,J.jsx)(`text`,{className:`topo-label`,x:9,y:16,children:(e.label||e.id).slice(0,16)}),i?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`text`,{className:`topo-sub`,x:9,y:30,children:(e.board||e.ip||``).slice(0,18)}),(0,J.jsx)(`rect`,{className:`topo-bar-bg`,x:9,y:37,width:a-18,height:4,rx:2}),(0,J.jsx)(`rect`,{x:9,y:37,width:(a-18)*Math.min(100,e.cpuLoad??0)/100,height:4,rx:2,style:{fill:VY(e.cpuLoad)}}),(0,J.jsx)(`rect`,{className:`topo-bar-bg`,x:9,y:43,width:a-18,height:4,rx:2}),(0,J.jsx)(`rect`,{x:9,y:43,width:(a-18)*Math.min(100,e.memUsedPct??0)/100,height:4,rx:2,style:{fill:VY(e.memUsedPct)}})]}):(0,J.jsx)(`text`,{className:`topo-sub`,x:9,y:29,children:e.onboardable?`+ onboard`:e.ip||e.mac||``})]},e.id)})]}),(0,J.jsxs)(`div`,{className:`topo-foot`,children:[(0,J.jsxs)(`span`,{className:`legend`,children:[(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{className:`dot`,style:{background:`#d4d4d8`}}),` online`]}),(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{className:`dot`,style:{background:`#f87171`}}),` offline`]}),(0,J.jsxs)(`span`,{children:[(0,J.jsx)(`i`,{className:`dot`,style:{background:`#e4e4e7`}}),` neighbour`]}),(0,J.jsxs)(`span`,{className:`muted`,children:[e.stats.devices,` devices · `,e.stats.neighbors,` discovered ·`,` `,e.stats.onboardable,` onboardable`]})]}),_&&(0,J.jsxs)(`div`,{className:`topo-pop`,children:[(0,J.jsxs)(`div`,{className:`topo-pop__hd`,children:[(0,J.jsx)(`strong`,{children:_.label}),(0,J.jsx)(`span`,{className:`muted`,children:[_.board,_.version,_.mac].filter(Boolean).join(` · `)||`no details advertised`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsx)(`button`,{className:`topo-btn`,onClick:()=>r(null),children:`✕`})]}),_.suggestedConfig?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`div`,{className:`muted`,style:{margin:`2px 0 6px`},children:`Not managed yet — add this to your device config to onboard it:`}),(0,J.jsx)(`pre`,{className:`topo-stub`,children:JSON.stringify({[_.suggestedConfig.name]:UY(_.suggestedConfig)},null,2)}),(0,J.jsx)(bc,{className:`topo-btn`,title:`Copy config stub`,label:`Copy config stub`,text:JSON.stringify({[_.suggestedConfig.name]:UY(_.suggestedConfig)},null,2)}),t&&(0,J.jsx)(`button`,{className:`topo-btn cfg-save`,onClick:()=>t(_.suggestedConfig.name,UY(_.suggestedConfig)),children:`Add to config →`})]}):(0,J.jsxs)(`div`,{className:`muted`,children:[`Managed device`,_.ip?` · ${_.ip}`:``,_.uptime?` · up ${_.uptime}`:``]})]})]})]})}function UY(e){let t={port:e.port,username:e.username};return e.host&&(t.host=e.host),e.mac&&(t.mac=e.mac),t}Ri.registerPlugin(q);function WY(e,t,n){let r=URL.createObjectURL(new Blob([t],{type:`${n};charset=utf-8`})),i=document.createElement(`a`);i.href=r,i.download=e,document.body.append(i),i.click(),i.remove(),URL.revokeObjectURL(r)}var GY=[{id:`overview`,label:`Overview`,sub:`Calls, latency & risk at a glance`},{id:`devices`,label:`Devices`,sub:`Connectivity radar & system health`},{id:`clients`,label:`Clients`,sub:`Connected LAN devices — usage, block/allow, pin IP`},{id:`aaa`,label:`RADIUS & UM`,sub:`RADIUS client & User Manager RADIUS server`},{id:`topology`,label:`Topology`,sub:`Layer-2 neighbours via MNDP / CDP / LLDP`},{id:`packets`,label:`Packets`,sub:`Live TZSP capture & decode`},{id:`snapshots`,label:`Snapshots`,sub:`Config history & time-travel diff`},{id:`plan`,label:`Change Plan`,sub:`Dry-run intended RouterOS commands`},{id:`s3`,label:`S3 Backups`,sub:`List, download & delete S3 backup objects`},{id:`backups`,label:`Backups`,sub:`Local config vault — create, restore, manage`},{id:`modules`,label:`Modules`,sub:`Enable/disable tool modules — curate the surface`},{id:`config`,label:`Config`,sub:`Effective configuration & safe editor`},{id:`feed`,label:`Live Feed`,sub:`Every tool call, in real time`}],KY=new Set(GY.map(e=>e.id)),qY=`mt-view`;function JY(){try{let e=location.hash.replace(/^#\/?/,``);return KY.has(e)?e:null}catch{return null}}function YY(){let e=JY();if(e)return e;try{let e=localStorage.getItem(qY);if(e&&KY.has(e))return e}catch{}return`overview`}var XY=[`#ededed`,`#a1a1a1`],ZY={overview:XY,devices:XY,clients:XY,aaa:XY,topology:XY,packets:XY,snapshots:XY,plan:XY,s3:XY,backups:XY,modules:XY,config:XY,feed:XY},QY={overview:{what:`A live pulse of all MCP tool activity: total calls, error rate, p50/p95 latency, the busiest tools, and a risk breakdown — over a time window you choose.`,tips:[`Change the time window (top-right) to zoom from the last 5 minutes out to 24 hours.`,`The risk donut splits calls by annotation: read · write · destructive · dangerous.`,`A rising error line usually points at one device or one tool — jump to Live Feed to see which.`]},devices:{what:`Every configured router with its live reachability (SSH or MAC-Telnet), latency, identity, and system health — CPU, memory and disk — refreshed continuously.`,tips:[`Each device gets a stable colour so you can track it across the connectivity radar.`,`Health (CPU/Mem/Disk) is probed periodically; MAC-Telnet devices are probed on a slower cadence.`,`Latency tiers are colour-coded green → amber → red; a grey node is currently unreachable.`]},clients:{what:`The LAN devices connected to a router — merged from its DHCP leases and ARP table — with live Download/Upload charts, and one-click controls to block/allow a device, pin (reserve) its IP, change that IP, or relabel it.`,tips:[`Pick the router (top-right) to inspect its connected devices; filter by IP, MAC or name.`,`Click a device to open its live ↓/↑ traffic chart — needs a simple queue targeting its IP.`,`Block/allow is enforced by MAC, so it survives the device changing IP; “Pin IP” makes its lease static.`]},aaa:{what:"Full management of the router's RADIUS client (`/radius`) and the built-in User Manager RADIUS server (`/user-manager`): RADIUS servers + incoming CoA, and User Manager users, service profiles, rate/quota limitations, NAS clients, profile assignments, accounting sessions, and global settings.",tips:[`Pick the router (top-right), then switch tabs across RADIUS, Users, Profiles, Limitations, NAS, Assignments, Sessions and Settings.`,`Every tab is full CRUD: add, edit, enable/disable and remove — secrets are write-only and shown redacted.`,`The Usage & Heatmap tab shows each user's 3-month download/upload and a GitHub-style connection heatmap, persisted locally.`,`If a device lacks the user-manager package, the User Manager tabs explain how to install it; RADIUS-client tabs still work.`]},topology:{what:`A Layer-2 map of neighbours each router discovers via MNDP / CDP / LLDP — the physical adjacency of your network, drawn live.`,tips:[`Solid nodes are configured devices; faint nodes are discovered-but-unmanaged neighbours.`,`Use “Add to config →” on an unmanaged neighbour to pre-fill it in the Config editor.`,`Drag to pan; the layout settles automatically as new neighbours arrive.`]},packets:{what:`Live packet capture streamed from a router over TZSP — decode headers in real time without leaving the dashboard.`,tips:[`Pick a device and start the capture; packets decode as they arrive.`,`Stop the capture when done — it frees the router-side sniffer.`,`Great for debugging a protocol issue alongside the Live Feed of tool calls.`]},snapshots:{what:`Point-in-time captures of a device’s full configuration (/export), stored locally so you can diff any two and see exactly what changed.`,tips:[`Capture a snapshot before a risky change, then diff after to audit the delta.`,`The diff is line-level: green added, red removed.`,`Snapshots are device config exports — for the dashboard’s OWN config history see the Config page.`]},plan:{what:`Dry-run the exact RouterOS commands a change would run before it touches a device — a change plan you can review and trust.`,tips:[`Paste or build intended commands to see them validated and ordered.`,`Nothing is sent to the device from here — it’s a preview.`,`Pair with Safe Mode (auto-revert) when you do apply for real.`]},s3:{what:`Browse, download, and delete backup objects in your configured S3-compatible bucket — your off-box archive of device backups and exports.`,tips:[`Filter by key prefix to find a device’s backups quickly.`,`Download fetches the object through a presigned URL; delete is permanent.`,`For host-side .rsc backups instead, use the Backups page.`]},backups:{what:`A local config vault on the MCP server: capture a device’s /export as a timestamped .rsc file, then download, upload, rename, restore (via Safe Mode), or delete it.`,tips:[`Restore offers a dry-run (applies then rolls back) before you commit for real.`,`Edit the vault path inline in the header — it’s saved to your config.`,`Filenames are stamped in the device’s local 24-hour clock.`]},modules:{what:"Every tool module in the catalog with a live on/off switch. Toggling one writes your config file's `tools` block (disabledModules / enabledModules) and applies it immediately, so you can curate exactly which scopes the MCP server exposes.",tips:[`MCP clients search-rank tools and degrade past ~100; trim the surface below ~150–200 tools so every remaining tool is reliably findable.`,`Disable adds the module to tools.disabledModules; enable removes it (or adds it to an active allow-list) — your config file is updated live on each toggle.`,`Changes need an MCP client reconnect (or a server restart) to actually shrink/grow the visible tool list.`]},config:{what:`View the effective configuration, edit it safely with schema-aware validation and auto-rollback, browse a full field guide, and travel through config version history.`,tips:[`Every successful apply is auto-saved to the version timeline — restore any point in time.`,`Save a named checkpoint before a big change for an easy, labelled rollback.`,`The Field Guide documents every config option, its type, and default — straight from the schema.`]},feed:{what:`Every tool call as it happens — tool, device, risk, duration, and success/error — with full request/response detail on click.`,tips:[`Filter by status to isolate failures, or by tool/device to follow one thread.`,`Click any row to open the full (secret-redacted) request and response.`,`Pause the stream when you want to inspect without rows shifting under you.`]}};function $Y({view:e}){let t=QY[e];return(0,J.jsxs)(`div`,{className:`pagehelp reveal`,role:`region`,"aria-label":`Page help`,children:[(0,J.jsx)(`div`,{className:`pagehelp__icon`,"aria-hidden":`true`,children:`?`}),(0,J.jsxs)(`div`,{className:`pagehelp__body`,children:[(0,J.jsx)(`p`,{className:`pagehelp__what`,children:t.what}),(0,J.jsx)(`ul`,{className:`pagehelp__tips`,children:t.tips.map((e,t)=>(0,J.jsx)(`li`,{children:e},t))})]})]})}function eX({name:e}){return(0,J.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.7`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,children:{overview:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`rect`,{x:`3`,y:`3`,width:`8`,height:`8`,rx:`1.6`}),(0,J.jsx)(`rect`,{x:`13`,y:`3`,width:`8`,height:`5`,rx:`1.6`}),(0,J.jsx)(`rect`,{x:`13`,y:`10`,width:`8`,height:`11`,rx:`1.6`}),(0,J.jsx)(`rect`,{x:`3`,y:`13`,width:`8`,height:`8`,rx:`1.6`})]}),devices:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`rect`,{x:`3`,y:`4`,width:`18`,height:`7`,rx:`2`}),(0,J.jsx)(`rect`,{x:`3`,y:`13`,width:`18`,height:`7`,rx:`2`}),(0,J.jsx)(`path`,{d:`M7 7.5h.01M7 16.5h.01`})]}),clients:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`circle`,{cx:`9`,cy:`8`,r:`3`}),(0,J.jsx)(`path`,{d:`M3.5 19a5.5 5.5 0 0 1 11 0`}),(0,J.jsx)(`path`,{d:`M16 7.5a2.5 2.5 0 0 1 0 5M17.5 19a4.5 4.5 0 0 0-2-3.6`})]}),aaa:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`path`,{d:`M12 3 4 6v5c0 4.4 3.2 7.6 8 9 4.8-1.4 8-4.6 8-9V6l-8-3Z`}),(0,J.jsx)(`path`,{d:`M9.5 11.5 11 13l3.5-3.5`})]}),topology:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`circle`,{cx:`12`,cy:`5`,r:`2.4`}),(0,J.jsx)(`circle`,{cx:`5`,cy:`19`,r:`2.4`}),(0,J.jsx)(`circle`,{cx:`19`,cy:`19`,r:`2.4`}),(0,J.jsx)(`path`,{d:`M12 7.4 6.4 16.6M12 7.4 17.6 16.6`})]}),packets:(0,J.jsx)(`path`,{d:`M3 12h4l2-7 4 14 2-7h6`}),snapshots:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`path`,{d:`M12 3 3 7.5 12 12 21 7.5 12 3Z`}),(0,J.jsx)(`path`,{d:`M3 12 12 16.5 21 12`}),(0,J.jsx)(`path`,{d:`M3 16.5 12 21 21 16.5`})]}),plan:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`circle`,{cx:`6`,cy:`6`,r:`2.3`}),(0,J.jsx)(`circle`,{cx:`6`,cy:`18`,r:`2.3`}),(0,J.jsx)(`circle`,{cx:`18`,cy:`8`,r:`2.3`}),(0,J.jsx)(`path`,{d:`M6 8.3v7.4M8.3 6H13a3 3 0 0 1 3 3v0`})]}),s3:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`ellipse`,{cx:`12`,cy:`6`,rx:`7`,ry:`2.6`}),(0,J.jsx)(`path`,{d:`M5 6v12c0 1.5 3.1 2.6 7 2.6s7-1.1 7-2.6V6`}),(0,J.jsx)(`path`,{d:`M5 12c0 1.5 3.1 2.6 7 2.6s7-1.1 7-2.6`})]}),backups:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`path`,{d:`M3 6.5 5 3.5h14l2 3`}),(0,J.jsx)(`rect`,{x:`3`,y:`6.5`,width:`18`,height:`14`,rx:`2`}),(0,J.jsx)(`path`,{d:`M9.5 12h5`})]}),modules:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`rect`,{x:`3`,y:`5`,width:`18`,height:`6`,rx:`3`}),(0,J.jsx)(`circle`,{cx:`8`,cy:`8`,r:`1.5`}),(0,J.jsx)(`rect`,{x:`3`,y:`13`,width:`18`,height:`6`,rx:`3`}),(0,J.jsx)(`circle`,{cx:`16`,cy:`16`,r:`1.5`})]}),config:(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`path`,{d:`M4 7h8M16 7h4M4 17h4M12 17h8`}),(0,J.jsx)(`circle`,{cx:`14`,cy:`7`,r:`2.2`}),(0,J.jsx)(`circle`,{cx:`10`,cy:`17`,r:`2.2`})]}),feed:(0,J.jsx)(`path`,{d:`M4 6h16M4 12h16M4 18h10`})}[e]})}function tX(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(null),[i,a]=(0,v.useState)(null),[o,s]=(0,v.useState)(null),[c,l]=(0,v.useState)(null),[u,d]=(0,v.useState)(!1),[f,p]=(0,v.useState)(null),[m,h]=(0,v.useState)([]),[g,_]=(0,v.useState)(36e5),[y,b]=(0,v.useState)(!1),[x,S]=(0,v.useState)(`off`),[C,w]=(0,v.useState)(null),[T,E]=(0,v.useState)(()=>new Set),[D,O]=(0,v.useState)({}),[k,A]=(0,v.useState)({tool:``,risk:``,device:``,status:``,q:``}),j=(0,v.useRef)(y);j.current=y;let M=(0,v.useRef)(null);NY(M);let[N,P]=(0,v.useState)(YY),F=(0,v.useCallback)(e=>{P(e);try{JY()!==e&&(location.hash=e),localStorage.setItem(qY,e)}catch{}},[]);(0,v.useEffect)(()=>{if(JY()!==N)try{location.hash=N}catch{}let e=()=>{let e=JY();e&&F(e)};return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]);let[I,L]=(0,v.useState)(()=>{try{let e=localStorage.getItem(`mt-help-open`);return new Set(e?JSON.parse(e):[])}catch{return new Set}}),ee=e=>L(t=>{let n=new Set(t);n.has(e)?n.delete(e):n.add(e);try{localStorage.setItem(`mt-help-open`,JSON.stringify([...n]))}catch{}return n}),[te,R]=(0,v.useState)(``),[z,ne]=(0,v.useState)(`all`),B=(0,v.useMemo)(()=>{let e=i?.devices??[];return{online:e.filter(e=>e.status.reachable===!0).length,offline:e.filter(e=>e.status.reachable===!1).length,total:e.length}},[i]),V=(0,v.useMemo)(()=>{let e=i?.devices??[],t=te.trim().toLowerCase();return e.filter(e=>!(z===`online`&&e.status.reachable!==!0||z===`offline`&&e.status.reachable!==!1||t&&!e.name.toLowerCase().includes(t)&&!(e.address??e.host??``).toLowerCase().includes(t)))},[i,te,z]);MY((0,v.useCallback)(e=>{if(j.current)return;h(t=>[e,...t].slice(0,kc));let t=e.device;t&&O(e=>({...e,[t]:(e[t]??0)+1}))},[]),(0,v.useCallback)(e=>S(e),[])),(0,v.useEffect)(()=>{fc(`/api/events?limit=${kc}`).then(e=>h(e.events)).catch(()=>{})},[]);let re=(0,v.useCallback)(()=>{fc(`/api/stats?window=${g}&buckets=60`).then(t).catch(()=>{})},[g]);(0,v.useEffect)(()=>{re();let e=()=>{re(),fc(`/api/meta`).then(r).catch(()=>{}),fc(`/api/devices`).then(a).catch(()=>{}),fc(`/api/topology`).then(s).catch(()=>{})};e();let t=setInterval(()=>{j.current||e()},4e3);return()=>clearInterval(t)},[re]),(0,v.useEffect)(()=>{let e=()=>void fc(`/api/config`).then(l).catch(()=>{});e();let t=setInterval(e,3e4);return()=>clearInterval(t)},[]),(0,v.useEffect)(()=>{let e=e=>{e.key===`Escape`&&w(null)};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[]);let ie=(0,v.useMemo)(()=>{let e=k.q.trim().toLowerCase();return m.filter(t=>!(k.tool&&t.tool!==k.tool||k.risk&&t.risk!==k.risk||k.device&&t.device!==k.device||k.status===`ok`&&t.isError||k.status===`error`&&!t.isError||e&&!t.tool.toLowerCase().includes(e)&&!t.input.toLowerCase().includes(e)&&!t.output.toLowerCase().includes(e)&&!(t.error??``).toLowerCase().includes(e))).sort((e,t)=>t.ts-e.ts)},[m,k]),ae=!!(k.tool||k.risk||k.device||k.status||k.q),oe=(0,v.useMemo)(()=>m.filter(e=>e.isError),[m]),se=(0,v.useMemo)(()=>({ok:m.length-oe.length,error:oe.length}),[m,oe]),H=(0,v.useCallback)(async e=>{try{w(await fc(`/api/event/${encodeURIComponent(e.id)}`))}catch{w(e)}},[]),ce=(0,v.useMemo)(()=>ie.slice(0,200),[ie]),le=(0,v.useMemo)(()=>ce.map(e=>e.id),[ce]),ue=le.length>0&&le.every(e=>T.has(e)),de=!ue&&le.some(e=>T.has(e)),fe=(0,v.useCallback)(e=>{E(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),pe=(0,v.useCallback)(()=>{E(e=>{let t=new Set(e);if(le.length>0&&le.every(e=>t.has(e)))for(let e of le)t.delete(e);else for(let e of le)t.add(e);return t})},[le]),[me,he]=(0,v.useState)(!1),ge=(0,v.useCallback)(async()=>{let e=[...T];if(e.length!==0)try{await mc({ids:e});let t=new Set(e);h(e=>e.filter(e=>!t.has(e.id))),E(new Set)}catch{}finally{he(!1)}},[T]),_e=e=>{if(e===`json`){WY(`mcp-events.json`,JSON.stringify(ie,null,2),`application/json`);return}let t=[`ts`,`tool`,`risk`,`device`,`durationMs`,`isError`,`error`],n=e=>/[",\n]/.test(e)?`"${e.replace(/"/g,`""`)}"`:e,r=ie.map(e=>[new Date(e.ts).toISOString(),e.tool,e.risk,e.device??``,String(e.durationMs),String(e.isError),e.error??``].map(n).join(`,`)).join(`
81
81
  `);WY(`mcp-events.csv`,`${t.join(`,`)}\n${r}\n`,`text/csv`)},ve=e?e.errorRate>=.2?`is-bad`:e.errorRate>=.05?`is-warn`:`is-good`:``,ye=c?.mcp??{},be=c?.dashboard??{},xe=(e,t,n)=>(0,J.jsxs)(`select`,{className:`btn`,value:k[e],onChange:t=>A(n=>({...n,[e]:t.target.value})),children:[(0,J.jsx)(`option`,{value:``,children:t}),n.map(e=>(0,J.jsx)(`option`,{value:e,children:e},e))]}),Se=GY.find(e=>e.id===N)??GY[0];return(0,J.jsxs)(`div`,{className:`shell`,ref:M,"data-view":N,style:{"--page-accent":ZY[N][0],"--page-accent-2":ZY[N][1]},children:[(0,J.jsxs)(`aside`,{className:`nav`,children:[(0,J.jsxs)(`div`,{className:`nav__brand`,children:[(0,J.jsx)(`div`,{className:`nav__mark`,children:(0,J.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,"aria-hidden":`true`,children:(0,J.jsxs)(`g`,{stroke:`#18181b`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,J.jsx)(`path`,{d:`M12 12 L4 5 M12 12 L20 5 M12 12 L12 20`}),(0,J.jsx)(`circle`,{cx:`12`,cy:`12`,r:`3`,fill:`#18181b`,stroke:`none`}),(0,J.jsx)(`circle`,{cx:`4`,cy:`5`,r:`1.9`,fill:`#18181b`,stroke:`none`}),(0,J.jsx)(`circle`,{cx:`20`,cy:`5`,r:`1.9`,fill:`#18181b`,stroke:`none`}),(0,J.jsx)(`circle`,{cx:`12`,cy:`20`,r:`1.9`,fill:`#18181b`,stroke:`none`})]})})}),(0,J.jsxs)(`div`,{className:`nav__brandtext`,children:[(0,J.jsx)(`b`,{children:`MikroTik MCP`}),(0,J.jsx)(`small`,{children:`Observability`})]})]}),(0,J.jsx)(`nav`,{className:`nav__items`,children:GY.map(e=>(0,J.jsxs)(`button`,{className:`nav__item${N===e.id?` is-active`:``}`,onClick:()=>F(e.id),children:[(0,J.jsx)(eX,{name:e.id}),(0,J.jsx)(`span`,{children:e.label}),e.id===`feed`&&m.length>0&&(0,J.jsx)(`span`,{className:`nav__badge`,children:m.length>999?`999+`:m.length},m.length)]},e.id))}),(0,J.jsxs)(`div`,{className:`nav__foot`,children:[(0,J.jsxs)(`span`,{className:`hero__live${x===`off`?``:` is-on`}${x===`ws`?` is-ws`:x===`sse`?` is-sse`:``}`,title:`Live transport: WebSocket (preferred) or SSE fallback`,children:[(0,J.jsx)(`span`,{className:`dot`}),x===`off`?`offline`:`live · ${x}`]}),(0,J.jsx)(`small`,{className:`muted`,children:n?`${wc(n.total)} events · ${n.transport}`:`connecting…`})]})]}),(0,J.jsxs)(`main`,{className:`main`,"data-view":N,children:[(0,J.jsxs)(`header`,{className:`topline reveal`,children:[(0,J.jsxs)(`div`,{className:`topline__txt`,children:[(0,J.jsx)(`h1`,{children:Se.label}),(0,J.jsx)(`small`,{children:Se.sub})]}),(0,J.jsx)(`span`,{className:`topline__spacer`}),N===`overview`&&(0,J.jsx)(`select`,{className:`btn`,value:g,onChange:e=>_(Number(e.target.value)),title:`Stats time window`,children:Oc.map(([e,t])=>(0,J.jsxs)(`option`,{value:t,children:[`window: `,e]},t))}),(0,J.jsxs)(`button`,{className:`help-toggle${I.has(N)?` is-on`:``}`,onClick:()=>ee(N),"aria-expanded":I.has(N),title:`About this page`,children:[(0,J.jsx)(`span`,{className:`help-toggle__q`,"aria-hidden":`true`,children:`?`}),`Help`]})]}),I.has(N)&&(0,J.jsx)($Y,{view:N}),N===`overview`&&(0,J.jsxs)(`section`,{className:`view`,children:[(0,J.jsx)(`div`,{className:`cards reveal`,children:e?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(_c,{k:`Calls (window)`,v:wc(e.total)}),(0,J.jsx)(_c,{k:`Calls / min`,v:e.callsPerMin.toFixed(1)}),(0,J.jsx)(_c,{k:`Error rate`,v:`${(e.errorRate*100).toFixed(1)}%`,sub:`${e.errors} err`,cls:ve}),(0,J.jsx)(_c,{k:`Avg latency`,v:xc(e.latency.avg)}),(0,J.jsx)(_c,{k:`p95 latency`,v:xc(e.latency.p95)}),(0,J.jsx)(_c,{k:`p99 latency`,v:xc(e.latency.p99)}),(0,J.jsx)(_c,{k:`Distinct tools`,v:wc(e.distinctTools)}),(0,J.jsx)(_c,{k:`Output volume`,v:Sc(e.outputBytes)})]}):(0,J.jsxs)(`div`,{className:`stat`,children:[(0,J.jsx)(`p`,{className:`k`,children:`Loading…`}),(0,J.jsx)(`div`,{className:`v`,children:`—`})]})}),(0,J.jsxs)(`div`,{className:`bento reveal`,children:[(0,J.jsx)(gc,{title:`Calls over time`,className:`b-series`,children:e?(0,J.jsx)(zJ,{series:e.series}):(0,J.jsx)(`div`,{className:`muted`,children:`no data`})}),e&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(gc,{title:`By risk`,className:`b-risk`,children:(0,J.jsx)(BJ,{segments:Object.keys(e.byRisk).map(t=>({label:t,value:e.byRisk[t],color:Dc[t]}))})}),(0,J.jsx)(gc,{title:`Top tools`,className:`b-tools`,children:(0,J.jsx)(vc,{rows:e.byTool.map(e=>({label:e.tool,value:e.count,sub:`${e.count}× · ${xc(e.p95Ms)} p95${e.errors?` · ${e.errors} err`:``}`,color:e.errors?`var(--mt-bad)`:void 0}))})}),(0,J.jsx)(gc,{title:`Status`,className:`b-status`,children:(0,J.jsx)(BJ,{centerLabel:`calls`,segments:[{label:`ok`,value:se.ok,color:`#a1a1a1`},{label:`error`,value:se.error,color:`#ff5c5c`}]})}),(0,J.jsx)(gc,{title:`By device`,className:`b-device`,children:e.byDevice.length?(0,J.jsx)(vc,{rows:e.byDevice.map(e=>({label:e.device,value:e.count}))}):(0,J.jsx)(`div`,{className:`muted`,children:`single device`})}),(0,J.jsx)(gc,{title:`Recent errors`,className:`b-errors`,children:oe.length?(0,J.jsx)(`div`,{className:`hbar`,children:oe.slice(0,8).map(e=>(0,J.jsxs)(`div`,{className:`hbar__row conn-errrow`,style:{gridTemplateColumns:`auto 1fr`},onClick:()=>void H(e),children:[(0,J.jsx)(`span`,{className:`muted`,children:Cc(e.ts)}),(0,J.jsxs)(`span`,{style:{color:`var(--mt-bad)`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,minWidth:0},title:e.error??e.output,children:[e.tool,`: `,e.error??e.output??`error`]})]},e.id))}):(0,J.jsx)(`div`,{className:`muted`,children:`no errors 🎉`})})]})]})]}),N===`devices`&&(i&&i.devices.length>0?(0,J.jsxs)(`section`,{className:`view`,children:[(0,J.jsxs)(`div`,{className:`dev-toolbar reveal`,children:[(0,J.jsx)(`input`,{className:`search`,type:`search`,placeholder:`Search devices by name or address…`,value:te,onChange:e=>R(e.target.value),style:{flex:1,minWidth:180}}),(0,J.jsx)(`div`,{className:`dev-filters`,children:[`all`,`online`,`offline`].map(e=>(0,J.jsx)(`button`,{className:`dev-fbtn${z===e?` is-active`:``}`,onClick:()=>ne(e),children:e===`all`?`All ${B.total}`:e===`online`?`Online ${B.online}`:`Offline ${B.offline}`},e))}),(0,J.jsxs)(`span`,{className:`muted`,children:[V.length,`/`,B.total,` shown`]})]}),(0,J.jsxs)(`details`,{className:`dev-collapse reveal`,open:B.total<=8,children:[(0,J.jsxs)(`summary`,{children:[`Connectivity radar`,(0,J.jsxs)(`span`,{className:`muted`,children:[` `,`· `,B.online,` online · `,B.offline,` offline ·`,` `,B.total,` total`]})]}),(0,J.jsx)(DY,{payload:i,pulses:D})]}),V.length===0?(0,J.jsxs)(`div`,{className:`feed-empty reveal`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`🔍`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`No devices match`}),(0,J.jsx)(`p`,{className:`feed-empty__sub`,children:`Try a different search or status filter.`})]}):(0,J.jsx)(`div`,{className:`dev-grid-wide reveal`,children:V.map(e=>(0,J.jsx)(OY,{d:e},e.name))}),V.length>0&&(0,J.jsx)(gc,{title:`Device system health`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:`CPU · memory · disk · latency · live probe`}),children:(0,J.jsx)(`div`,{className:`health-grid`,children:V.map(e=>(0,J.jsx)(jY,{d:e},e.name))})})]}):(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`🖧`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`No devices configured`}),(0,J.jsx)(jc,{type:`secondary`,label:`Tip`,children:`Add a device to your config to see connectivity and system health here.`})]})),N===`clients`&&(0,J.jsx)(XJ,{}),N===`aaa`&&(0,J.jsx)(pl,{}),N===`topology`&&(o&&o.nodes.length>0?(0,J.jsx)(`section`,{className:`view`,children:(0,J.jsx)(gc,{title:`Network topology`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:`Layer-2 neighbours via MNDP/CDP/LLDP · click a neighbour to onboard it`}),children:(0,J.jsx)(HY,{topo:o,onOnboard:(e,t)=>{p({name:e,body:t}),d(!0),F(`config`)}})})}):(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`🛰️`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`No neighbours discovered yet`}),(0,J.jsx)(`p`,{className:`feed-empty__sub`,children:`Layer-2 neighbours (MNDP / CDP / LLDP) appear here as the device reports them.`})]})),N===`packets`&&(0,J.jsx)(`section`,{className:`view`,children:(0,J.jsx)(gc,{title:`Packet capture`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:`live TZSP decode · /tool sniffer streaming`}),children:(0,J.jsx)(RY,{})})}),N===`snapshots`&&(0,J.jsx)(BY,{}),N===`plan`&&(0,J.jsx)(hl,{}),N===`s3`&&(0,J.jsx)(zY,{}),N===`backups`&&(0,J.jsx)(Lc,{}),N===`modules`&&(0,J.jsx)(FY,{}),N===`config`&&(c?(0,J.jsxs)(`section`,{className:`view`,children:[(0,J.jsx)(gc,{title:`Configuration`,className:`reveal`,extra:(0,J.jsx)(`button`,{className:`btn`,onClick:()=>d(e=>!e),title:`Edit the config JSON with autocomplete, validation and safe-apply`,children:u?`View`:`✎ Edit config`}),children:u?(0,J.jsx)(mY,{initial:f?{...c,devices:{...c.devices,[f.name]:f.body}}:c,onClose:()=>{d(!1),p(null)},onReload:()=>{p(null),fc(`/api/config`).then(l).catch(()=>{})}},f?`seed-${f.name}`:`config`):(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`div`,{className:`legend`,style:{margin:`0 0 10px`},children:[(0,J.jsxs)(`span`,{children:[`transport: `,Tc(ye.transport)]}),(0,J.jsxs)(`span`,{children:[`read-only: `,c.readOnly?`yes`:`no`]}),(0,J.jsxs)(`span`,{children:[`dashboard: `,Tc(be.host),`:`,Tc(be.port)]}),(0,J.jsxs)(`span`,{children:[`capture: `,be.captureBody?`on`:`off`]}),(0,J.jsxs)(`span`,{children:[`s3: `,c.s3?`configured`:`off`]})]}),(0,J.jsxs)(`details`,{className:`cfg`,children:[(0,J.jsx)(`summary`,{children:`Full effective configuration (secrets redacted)`}),(0,J.jsx)(uY,{value:c,maxHeight:340})]})]})}),(0,J.jsx)(gc,{title:`Version history`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:`point-in-time snapshots · diff & restore`}),children:(0,J.jsx)($J,{onRestored:()=>void fc(`/api/config`).then(l).catch(()=>{})})}),(0,J.jsx)(gc,{title:`Field guide`,className:`reveal`,extra:(0,J.jsx)(`span`,{className:`muted`,children:`every config option, documented from the schema`}),children:(0,J.jsx)(tY,{})})]}):(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(Pc,{}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`Loading configuration…`})]})),N===`feed`&&(0,J.jsxs)(`div`,{className:`panel reveal`,children:[(0,J.jsxs)(`div`,{className:`sheet__hd`,style:{marginBottom:12},children:[(0,J.jsx)(`h2`,{style:{margin:0},children:`Live tool calls`}),(0,J.jsx)(`span`,{style:{flex:1}}),(0,J.jsxs)(`span`,{className:`muted`,children:[ie.length,` shown · `,m.length,` buffered`]})]}),(0,J.jsxs)(`div`,{className:`toolbar`,style:{marginBottom:12},children:[(0,J.jsx)(`div`,{className:`grow`,style:{flex:1,minWidth:180},children:(0,J.jsx)(`input`,{className:`search`,type:`search`,placeholder:`Search tool / input / output / error…`,value:k.q,onChange:e=>A(t=>({...t,q:e.target.value}))})}),xe(`tool`,`all tools`,n?.tools??[]),xe(`risk`,`all risk`,[`READ`,`WRITE`,`WRITE_IDEMPOTENT`,`DESTRUCTIVE`,`DANGEROUS`]),xe(`device`,`all devices`,n?.devices??[]),xe(`status`,`all status`,[`ok`,`error`]),(0,J.jsx)(`button`,{className:`btn${y?` is-active`:``}`,onClick:()=>b(e=>!e),children:y?`▶ Resume`:`⏸ Pause`}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>_e(`csv`),children:`CSV`}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>_e(`json`),children:`JSON`}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>A({tool:``,risk:``,device:``,status:``,q:``}),children:`Clear`}),me&&T.size>0?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsxs)(`button`,{className:`btn btn-danger`,onClick:()=>void ge(),children:[`✓ Confirm delete (`,T.size,`)`]}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>he(!1),children:`Cancel`})]}):(0,J.jsxs)(`button`,{className:`btn`,disabled:T.size===0,onClick:()=>he(!0),title:`Delete the selected rows`,children:[`🗑 Delete`,T.size>0?` (${T.size})`:``]})]}),ie.length===0?ae?(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__icon`,children:`🔍`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:`No calls match your filters`}),(0,J.jsxs)(`p`,{className:`feed-empty__sub`,children:[m.length,` call`,m.length===1?``:`s`,` buffered — try widening the search or the risk / device / status filters.`]}),(0,J.jsx)(`button`,{className:`btn`,onClick:()=>A({tool:``,risk:``,device:``,status:``,q:``}),children:`Clear filters`})]}):(0,J.jsxs)(`div`,{className:`feed-empty`,children:[(0,J.jsx)(`div`,{className:`feed-empty__pulse${x===`off`?``:` is-on`}`}),(0,J.jsx)(`p`,{className:`feed-empty__title`,children:x===`off`?`Not connected`:`Listening for tool calls…`}),(0,J.jsx)(`p`,{className:`feed-empty__sub`,children:x===`off`?`The live stream is offline — it will reconnect automatically.`:`Tool calls the LLM makes against this server stream in here in real time.`})]}):(0,J.jsx)(`div`,{className:`feedwrap`,children:(0,J.jsxs)(`table`,{className:`feed`,children:[(0,J.jsx)(`thead`,{children:(0,J.jsxs)(`tr`,{children:[(0,J.jsx)(`th`,{style:{width:28},children:(0,J.jsx)(`input`,{type:`checkbox`,"aria-label":`Select all shown rows`,checked:ue,ref:e=>{e&&(e.indeterminate=de)},onChange:pe})}),(0,J.jsx)(`th`,{children:`time`}),(0,J.jsx)(`th`,{children:`tool`}),(0,J.jsx)(`th`,{children:`risk`}),(0,J.jsx)(`th`,{children:`device`}),(0,J.jsx)(`th`,{className:`num`,children:`dur`}),(0,J.jsx)(`th`,{children:`status`}),(0,J.jsx)(`th`,{children:`output`})]})}),(0,J.jsx)(`tbody`,{children:ce.map(e=>(0,J.jsxs)(`tr`,{className:`${e.isError?`is-err`:``}${T.has(e.id)?` is-selected`:``}`.trim()||void 0,onClick:()=>void H(e),children:[(0,J.jsx)(`td`,{onClick:e=>e.stopPropagation(),children:(0,J.jsx)(`input`,{type:`checkbox`,"aria-label":`Select row`,checked:T.has(e.id),onChange:()=>fe(e.id)})}),(0,J.jsx)(`td`,{children:Cc(e.ts)}),(0,J.jsx)(`td`,{children:e.tool}),(0,J.jsx)(`td`,{children:(0,J.jsx)(`span`,{className:`risk risk-${e.risk}`,children:e.risk.replace(`WRITE_IDEMPOTENT`,`WRITE·I`)})}),(0,J.jsx)(`td`,{children:e.device??`—`}),(0,J.jsx)(`td`,{className:`num`,children:xc(e.durationMs)}),(0,J.jsx)(`td`,{children:(0,J.jsx)(`span`,{className:e.isError?`status-err`:`status-ok`,children:e.isError?`error`:`ok`})}),(0,J.jsx)(`td`,{className:`preview`,children:e.isError?e.error??`error`:e.output||`—`})]},e.id))})]})})]})]}),C&&(0,J.jsx)(kY,{event:C,onClose:()=>w(null)})]})}var nX=document.getElementById(`root`);nX&&(0,y.createRoot)(nX).render((0,J.jsx)(tX,{}));
82
82
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usex/mikrotik-mcp",
3
- "version": "3.35.0",
3
+ "version": "3.37.0",
4
4
  "description": "MCP server for MikroTik RouterOS — 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
5
5
  "keywords": [
6
6
  "ai",
@@ -93,7 +93,7 @@
93
93
  "class-variance-authority": "^0.7.1",
94
94
  "clsx": "^2.1.1",
95
95
  "gsap": "^3.15.0",
96
- "prettier": "^3.9.3",
96
+ "prettier": "^3.9.4",
97
97
  "react": "^19.2.7",
98
98
  "react-dom": "^19.2.7",
99
99
  "recharts": "^3.9.0",