@mmmbuto/nexuscrew 0.8.52-rc.13 → 0.8.52-rc.15
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.
- package/frontend/dist/assets/{index-Cjq8zHOy.js → index-CB3o67BZ.js} +1 -1
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/sw.js +11 -0
- package/frontend/dist/version.json +1 -1
- package/lib/cells/scope-guard.js +155 -0
- package/lib/cells/scope.js +112 -0
- package/lib/mcp/tools.js +5 -1
- package/lib/nodes/store.js +35 -0
- package/lib/proxy/federation.js +13 -2
- package/lib/server.js +66 -4
- package/package.json +1 -1
|
@@ -90,4 +90,4 @@ Minimum version required to store current data is: `+B+`.
|
|
|
90
90
|
`).filter(o=>o!==""),promptMode:e.promptMode};return e.modelFlag&&(s.model={flag:e.modelFlag,value:e.modelValue||""}),e.promptMode==="flag"&&(s.promptFlag=e.promptFlag),t&&(s.env=Object.fromEntries(e.envRows.filter(o=>!o.remove&&o.key).map(o=>[o.key,o.value]))),s}function $s({children:e,onClose:t,label:n,error:s=""}){const o=j.useRef(null),a=j.useRef(null),u=j.useRef(t);return u.current=t,j.useEffect(()=>{const h=document.activeElement,p=o.current,f=()=>Array.from((p==null?void 0:p.querySelectorAll('button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'))||[]).filter(y=>y.offsetParent!==null),v=requestAnimationFrame(()=>{var y;return(y=f()[0]||p)==null?void 0:y.focus({preventScroll:!0})}),S=y=>{var C;if(y.key==="Escape"){y.preventDefault(),(C=u.current)==null||C.call(u);return}if(y.key!=="Tab")return;const b=f();if(!b.length){y.preventDefault(),p==null||p.focus();return}const w=b[0],k=b[b.length-1];y.shiftKey&&document.activeElement===w?(y.preventDefault(),k.focus()):!y.shiftKey&&document.activeElement===k&&(y.preventDefault(),w.focus())};return document.addEventListener("keydown",S),()=>{cancelAnimationFrame(v),document.removeEventListener("keydown",S),h&&h.isConnected&&typeof h.focus=="function"&&h.focus({preventScroll:!0})}},[]),j.useEffect(()=>{s&&requestAnimationFrame(()=>{var h;return(h=a.current)==null?void 0:h.scrollIntoView({block:"nearest"})})},[s]),c.jsx("div",{className:"nc-fleet-modal",role:"presentation",onMouseDown:h=>{h.target===h.currentTarget&&t()},children:c.jsxs("div",{ref:o,className:"nc-fleet-modal-dialog",role:"dialog","aria-modal":"true","aria-label":n||m("settings"),tabIndex:-1,children:[e,s&&c.jsx("div",{ref:a,className:"nc-err nc-fleet-modal-error",role:"alert","aria-live":"assertive",children:s})]})})}function zd({token:e,targets:t=[],readonly:n=!1,onPower:s,onImport:o}){const[a,u]=j.useState([]),[h,p]=j.useState(0);j.useEffect(()=>{let C=!0;async function R(){const F=[{route:[],label:m("local")}].concat((t||[]).map(K=>({route:Array.isArray(K.route)?K.route:[],label:K.label||(K.route||[]).join(" › ")}))),O=await Promise.all(F.map(async K=>{const Y={...K,available:!1,readonly:!1,capabilities:[],provider:null,cells:[],unmanaged:[],err:""};try{const z=await ws(e,K.route);Y.available=!!z.available,Y.capabilities=z.capabilities||[],Y.provider=z.provider||null,Y.cells=(z.cells||[]).map(Z=>({...Z,route:K.route}));try{Y.readonly=!!(await Zh(e,K.route)).readonlyDefault}catch{}try{const Z=await Jh(e,K.route),G=new Set(Y.cells.map(Q=>Q.tmuxSession).filter(Boolean));Y.unmanaged=(Z.sessions||[]).filter(Q=>Q&&!G.has(Q.name))}catch{}}catch(z){Y.err=String(z&&z.message||z)}return Y}));C&&u(O)}R();const E=setInterval(R,6e3);return()=>{C=!1,clearInterval(E)}},[e,t,h]);const f=(C,R)=>Array.isArray(C.capabilities)&&C.capabilities.includes(R)&&!n&&!C.readonly,v=()=>p(C=>C+1),S=(C,R)=>{s&&s({...C,route:R})},y=(C,R)=>{s&&s({...C,route:R})},b=async(C,R)=>{try{await Fo(e,C.cell,R)}catch{}v()},w=async(C,R)=>{if(window.confirm(m("fleet-remove-cell").replace("{id}",C.cell))){try{await J_(e,C.cell,!0,R)}catch{}v()}},k=async(C,R)=>{if(window.confirm(m("terminate-confirm").replace("{name}",C.name))){try{await Xh(e,C.name,R)}catch{}v()}};return c.jsxs("div",{className:"nc-fleet-inventory",children:[c.jsxs("div",{className:"nc-fleet-section-head",children:[c.jsx("b",{children:m("fleet-inventory")}),c.jsx("small",{children:m("fleet-inventory-help")})]}),a.map(C=>{const R=C.route.length?C.route.join("/"):"local";return c.jsxs("div",{className:"nc-fleet-pos",children:[c.jsxs("div",{className:"nc-fleet-pos-title",children:[c.jsx("span",{className:`dot ${C.available?"on":"warn"}`}),c.jsx("b",{children:C.label}),c.jsx("small",{children:C.available?`${C.cells.length} ${m("fleet-cells")} · ${C.unmanaged.length} ${m("fleet-tmux")}`:C.err||m("fleet-not-available")})]}),C.cells.map(E=>c.jsxs("div",{className:"nc-fleet-item nc-fleet-cell",children:[c.jsxs("span",{children:[c.jsx("b",{children:E.cell}),c.jsx("small",{children:`${E.engine||""}${E.key?`·${E.key}`:""}${E.active?"":` · ${m("cell-off")}`}`})]}),c.jsxs("span",{className:"nc-fleet-cell-actions",children:[E.active&&f(C,"down")&&c.jsx("button",{className:"nc-btn ghost",title:m("power-off"),onClick:()=>y(E,C.route),children:m("stop")}),!E.active&&f(C,"up")&&c.jsx("button",{className:"nc-btn ghost",title:m("power-on"),onClick:()=>S(E,C.route),children:m("start")}),f(C,"restart")&&E.active&&c.jsx("button",{className:"nc-btn ghost",title:m("restart"),onClick:()=>b(E,C.route),children:m("restart")}),f(C,"remove")&&c.jsx("button",{className:"nc-btn danger",title:m("delete"),onClick:()=>w(E,C.route),children:"×"})]})]},`${R}:${E.cell}`)),C.unmanaged.map(E=>c.jsxs("div",{className:"nc-fleet-item nc-fleet-unmanaged",children:[c.jsxs("span",{children:[c.jsx("b",{children:E.name}),c.jsx("small",{children:m("fleet-tmux")})]}),c.jsxs("span",{className:"nc-fleet-cell-actions",children:[f(C,"import")&&o&&c.jsx("button",{className:"nc-btn ghost",title:m("import-as-cell"),onClick:()=>o(E,C.route),children:m("import-as-cell")}),c.jsx("button",{className:"nc-btn danger",title:m("terminate"),onClick:()=>k(E,C.route),children:"×"})]})]},`${R}:u:${E.name}`)),C.available&&C.cells.length===0&&C.unmanaged.length===0&&c.jsx("div",{className:"nc-empty",children:m("fleet-inventory-empty")})]},R)})]})}function J2({cells:e=[],engines:t=[],busy:n,canRestore:s=!1,onRestore:o,onClose:a}){const[u,h]=j.useState("export"),[p,f]=j.useState(()=>new Set(e.map(N=>N.id))),[v,S]=j.useState(()=>new Set(t.map(N=>N.id))),[y,b]=j.useState([]),[w,k]=j.useState([]),[C,R]=j.useState(""),E=new Set(t.map(N=>N.id)),F=new Set(e.map(N=>N.id)),O=(N,T)=>N(L=>{const $=new Set(L);return $.has(T)?$.delete(T):$.add(T),$}),K=()=>{const N=K2(e,p,t,v);if(N.ok===!1){R(m(`fleet-backup-${N.error}`));return}const T=e.filter(I=>p.has(I.id)).length,L=t.filter(I=>v.has(I.id)).length;if(N.cells.length!==T){R(m("fleet-backup-invalid-cell"));return}if(N.engines.length!==L){R(m("fleet-backup-invalid-engine"));return}if(!N.cells.length&&!N.engines.length){R(m("fleet-backup-select-one"));return}const $=new Blob([`${JSON.stringify(N,null,2)}
|
|
91
91
|
`],{type:"application/json"}),U=URL.createObjectURL($),x=document.createElement("a");x.href=U,x.download=`nexuscrew-fleet-${new Date().toISOString().slice(0,10)}.json`,document.body.appendChild(x),x.click(),x.remove(),setTimeout(()=>URL.revokeObjectURL(U),0),R("")},Y=async N=>{if(R(""),b([]),k([]),!!N){if(N.size>1024*1024){R(m("fleet-backup-too-large"));return}try{const T=q2(await N.text());if(!T.ok){R(m(`fleet-backup-${T.error}`));return}const L=new Set(T.engines.map($=>$.id));k(T.engines.map($=>({engine:$,exists:E.has($.id),selected:!E.has($.id)}))),b(T.cells.map($=>{const U=E.has($.engine)||L.has($.engine),x=F.has($.id);return{cell:$,engine:U?$.engine:"",selected:U&&!x,exists:x}}))}catch(T){R(String(T.message||T))}}},z=(N,T)=>b(L=>L.map(($,U)=>U===N?{...$,...T}:$)),Z=(N,T)=>k(L=>L.map(($,U)=>U===N?{...$,...T}:$)),G=new Set([...E,...w.filter(N=>N.selected).map(N=>N.engine.id)]),Q=y.filter(N=>N.selected&&N.engine&&G.has(N.engine)),B=w.filter(N=>N.selected),M=[...new Map([...t.map(N=>[N.id,N.label||N.id]),...w.filter(N=>N.selected).map(N=>[N.engine.id,N.engine.label||N.engine.id])]).entries()];return c.jsxs("div",{className:"nc-set-form nc-fleet-form nc-backup-dialog",children:[c.jsx("b",{children:m("fleet-backup")}),c.jsx("small",{children:m("fleet-backup-help")}),c.jsxs("div",{className:"nc-set-tabs nc-backup-tabs",children:[c.jsx("button",{type:"button",className:`nc-set-tabbtn${u==="export"?" on":""}`,onClick:()=>{h("export"),R("")},children:m("fleet-backup-export")}),c.jsx("button",{type:"button",className:`nc-set-tabbtn${u==="import"?" on":""}`,disabled:!s,title:s?"":m("fleet-backup-restore-unavailable"),onClick:()=>{h("import"),R("")},children:m("fleet-backup-import")})]}),u==="export"?c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{f(new Set(e.map(N=>N.id))),S(new Set(t.map(N=>N.id)))},children:m("select-all")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{f(new Set),S(new Set)},children:m("select-none")})]}),c.jsxs("div",{className:"nc-backup-list",children:[c.jsx("b",{children:m("fleet-engines")}),t.map(N=>{var T;return c.jsxs("label",{className:"nc-check nc-backup-row",children:[c.jsx("input",{type:"checkbox",checked:v.has(N.id),onChange:()=>O(S,N.id)}),c.jsxs("span",{children:[c.jsx("b",{children:N.label||N.id}),c.jsxs("small",{children:[N.id,(T=N.envKeys)!=null&&T.length?` · ${N.envKeys.length} ${m("fleet-backup-env-names")}`:""]})]})]},N.id)}),c.jsx("b",{children:m("fleet-cells")}),e.map(N=>c.jsxs("label",{className:"nc-check nc-backup-row",children:[c.jsx("input",{type:"checkbox",checked:p.has(N.id),onChange:()=>O(f,N.id)}),c.jsxs("span",{children:[c.jsx("b",{children:N.id}),c.jsxs("small",{children:[N.engine," · ",m("fleet-system-prompt")," ",(N.prompt||"").length," ",m("characters")]})]})]},N.id)),!e.length&&!t.length&&c.jsx("div",{className:"nc-empty",children:m("fleet-backup-empty")})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:a,children:m("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:!p.size&&!v.size,onClick:K,children:m("fleet-backup-download")})]})]}):c.jsxs(c.Fragment,{children:[c.jsx("input",{type:"file",accept:"application/json,.json",disabled:n,onChange:N=>Y(N.target.files&&N.target.files[0])}),c.jsx("small",{children:m("fleet-backup-import-help")}),!!(y.length||w.length)&&c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{k(N=>N.map(T=>({...T,selected:!0}))),b(N=>N.map(T=>({...T,selected:!!T.engine})))},children:m("select-all")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{k(N=>N.map(T=>({...T,selected:!1}))),b(N=>N.map(T=>({...T,selected:!1})))},children:m("select-none")})]}),c.jsxs("div",{className:"nc-backup-list",children:[!!w.length&&c.jsx("b",{children:m("fleet-engines")}),w.map((N,T)=>{var L;return c.jsx("div",{className:"nc-backup-import-row",children:c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:N.selected,onChange:$=>Z(T,{selected:$.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:N.engine.label||N.engine.id}),c.jsxs("small",{children:[N.engine.id," · ",N.exists?m("fleet-backup-overwrite"):m("fleet-backup-new"),(L=N.engine.envKeys)!=null&&L.length?` · ${m("fleet-backup-env-values-required")}`:""]})]})]})},N.engine.id)}),!!y.length&&c.jsx("b",{children:m("fleet-cells")}),y.map((N,T)=>c.jsxs("div",{className:"nc-backup-import-row",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:N.selected,disabled:!N.engine||!G.has(N.engine),onChange:L=>z(T,{selected:L.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:N.cell.id}),c.jsxs("small",{children:[N.exists?m("fleet-backup-overwrite"):m("fleet-backup-new")," · ",m("fleet-system-prompt")," ",N.cell.systemPrompt.length," ",m("characters")]})]})]}),c.jsxs("select",{value:N.engine,onChange:L=>z(T,{engine:L.target.value,selected:!!L.target.value}),children:[c.jsx("option",{value:"",children:m("fleet-backup-engine-missing")}),M.map(([L,$])=>c.jsx("option",{value:L,children:$},L))]})]},N.cell.id))]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:a,children:m("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:n||!Q.length&&!B.length,onClick:()=>o({engineRows:w,cellRows:y}),children:m("fleet-backup-restore")})]})]}),C&&c.jsx("div",{className:"nc-err",children:C})]})}function Z2({state:e,setState:t,busy:n,onSave:s,catalog:o}){var R;const a=e.form,u=E=>t({...e,form:{...a,...E}}),h=a.envRows||[],p=[...new Map(o.map(E=>[E.client,E.clientLabel||E.client])).entries()],f=o.filter(E=>E.client===a.client),v=Zo(o,a),S=typeof(v==null?void 0:v.credentialEnv)=="string"?v.credentialEnv:"",y=Array.isArray(v==null?void 0:v.credentialUsedBy)?v.credentialUsedBy:[],b=(v==null?void 0:v.credentialSource)||"missing",w=!!(v!=null&&v.credentialEnv)||a.credentialProfile==="a"||a.credentialProfile==="p",k=!!S&&e.mode==="new"&&(v==null?void 0:v.authConfigured)!==!0&&!a.credentialValue&&!a.allowMissingCredential,C=E=>{E&&u({client:E.client,provider:E.provider,credentialProfile:E.credentialProfile||"",managedModel:E.model||"",protocol:E.protocol||"",permissionPolicy:E.permissionPolicyDefault||"standard",credentialSourcePolicy:"auto",rc:!!E.rc,displayName:E.custom?m("fleet-custom-provider-default"):"",baseUrl:E.custom?"":E.endpoint||"",envKey:E.defaultEnvKey||"",providerId:"nexuscrew-custom",credentialValue:"",credentialReveal:!1,allowMissingCredential:!1,...e.mode==="new"?{id:E.id,label:""}:{}})};return c.jsxs("div",{className:"nc-set-form nc-fleet-form",children:[c.jsx("b",{children:e.mode==="new"?m("fleet-new-engine"):`${m("edit")} ${a.id}`}),c.jsx("input",{value:a.id,disabled:e.mode!=="new",placeholder:"id",onChange:E=>u({id:E.target.value})}),c.jsx("input",{value:a.label,placeholder:m("label"),onChange:E=>u({label:E.target.value})}),c.jsxs("select",{value:a.kind,disabled:e.mode!=="new",onChange:E=>u({kind:E.target.value}),children:[c.jsx("option",{value:"managed",children:m("fleet-managed")}),c.jsx("option",{value:"custom",children:m("fleet-custom")})]}),a.kind==="managed"?c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("select",{value:a.client,disabled:e.mode!=="new",onChange:E=>C(o.find(F=>F.client===E.target.value&&F.default)||o.find(F=>F.client===E.target.value)),children:p.map(([E,F])=>c.jsx("option",{value:E,children:F},E))}),c.jsx("select",{value:((R=Zo(o,a))==null?void 0:R.id)||"",disabled:e.mode!=="new",onChange:E=>C(o.find(F=>F.id===E.target.value)),children:f.map(E=>c.jsx("option",{value:E.id,children:E.label},E.id))})]}),a.client!=="shell"&&c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.managedModel,list:"nc-managed-models",placeholder:m(v!=null&&v.requiresModel?"fleet-model-required":"fleet-model-default"),onChange:E=>u({managedModel:E.target.value})}),c.jsx("datalist",{id:"nc-managed-models",children:[...(v==null?void 0:v.models)||[],...a.modelOptions||[]].filter((E,F,O)=>E&&O.indexOf(E)===F).map(E=>c.jsx("option",{value:E},E))})]}),v!=null&&v.supportsUnsafe?c.jsxs("select",{value:a.permissionPolicy,onChange:E=>u({permissionPolicy:E.target.value}),children:[c.jsx("option",{value:"standard",children:m("fleet-standard-permissions")}),c.jsx("option",{value:"unsafe",children:m("fleet-unsafe-permissions")})]}):c.jsx("small",{children:m("fleet-standard-permissions")}),(v==null?void 0:v.supportsUnsafe)&&a.permissionPolicy==="unsafe"&&c.jsx("small",{className:"nc-err",children:m("fleet-unsafe-warning")}),w&&c.jsxs("select",{"aria-label":m("fleet-credential-source-policy"),value:a.credentialSourcePolicy||"auto",onChange:E=>u({credentialSourcePolicy:E.target.value}),children:[c.jsx("option",{value:"auto",children:m("fleet-credential-source-policy-auto")}),c.jsx("option",{value:"nexuscrew-store",children:m("fleet-credential-source-policy-nexuscrew-store")}),c.jsx("option",{value:"environment",children:m("fleet-credential-source-policy-environment")})]}),(v==null?void 0:v.credentialEnv)===!0&&c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.envKey,placeholder:m("fleet-api-key-env"),onChange:E=>u({envKey:E.target.value})}),c.jsx("small",{children:m("fleet-custom-secret-help")})]}),S&&c.jsxs("section",{className:"nc-fleet-key","aria-label":m("fleet-key-section"),children:[c.jsxs("div",{className:"nc-fleet-key-head",children:[c.jsx("b",{children:m("fleet-key-section")}),c.jsx("span",{children:m(`fleet-credential-source-${b}`)})]}),c.jsxs("label",{className:"nc-field",children:[m("fleet-key-variable"),c.jsx("input",{value:S,readOnly:!0,"aria-label":m("fleet-key-variable")})]}),c.jsx("small",{children:y.length?m("fleet-key-used-by").replace("{engines}",y.join(", ")):m("fleet-key-used-by-none")}),y.length>0&&c.jsx("small",{children:m("fleet-key-shared-impact").replace("{key}",S).replace("{engines}",y.join(", "))}),c.jsxs("div",{className:"nc-fleet-key-input",children:[c.jsx("input",{type:a.credentialReveal?"text":"password",value:a.credentialValue||"","aria-label":m("fleet-key-value").replace("{key}",S),autoComplete:"new-password",spellCheck:!1,autoCapitalize:"none",autoCorrect:"off",placeholder:v!=null&&v.authConfigured||e.mode==="edit"?m("fleet-key-blank-keeps"):m("fleet-key-enter"),onChange:E=>u({credentialValue:E.target.value,allowMissingCredential:E.target.value?!1:a.allowMissingCredential})}),c.jsx("button",{type:"button",className:"nc-btn ghost","aria-label":m(a.credentialReveal?"fleet-key-hide":"fleet-key-reveal"),onClick:()=>u({credentialReveal:!a.credentialReveal}),children:m(a.credentialReveal?"fleet-key-hide":"fleet-key-reveal")})]}),c.jsx("small",{children:m("fleet-key-local-help")}),e.mode==="new"&&(v==null?void 0:v.authConfigured)!==!0&&!a.credentialValue&&c.jsxs("label",{className:"nc-check nc-fleet-key-missing",children:[c.jsx("input",{type:"checkbox",checked:!!a.allowMissingCredential,onChange:E=>u({allowMissingCredential:E.target.checked})}),m("fleet-key-create-missing")]}),(v==null?void 0:v.notice)&&c.jsx("small",{className:"nc-fleet-provider-notice",children:m(`fleet-provider-notice-${v.notice}`)})]}),(v==null?void 0:v.notice)&&!S&&c.jsx("small",{className:"nc-fleet-provider-notice",children:m(`fleet-provider-notice-${v.notice}`)}),a.provider==="custom"&&c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.displayName,placeholder:m("fleet-provider-display"),onChange:E=>u({displayName:E.target.value})}),c.jsx("input",{value:a.baseUrl,placeholder:"https://api.example.com/v1",onChange:E=>u({baseUrl:E.target.value})}),((v==null?void 0:v.protocols)||[]).length>1&&c.jsx("select",{value:a.protocol,onChange:E=>u({protocol:E.target.value}),children:v.protocols.map(E=>c.jsx("option",{value:E,children:E},E))}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:a.envKey,placeholder:m("fleet-api-key-env"),onChange:E=>u({envKey:E.target.value})}),c.jsx("input",{value:a.providerId,placeholder:m("fleet-provider-id"),onChange:E=>u({providerId:E.target.value})})]}),c.jsxs("small",{children:[a.protocol," · ",m("fleet-custom-secret-help")]})]}),c.jsx("small",{children:m(a.client==="shell"?"fleet-shell-engine-help":"fleet-managed-help")})]}):c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.command,placeholder:m("command-path"),onChange:E=>u({command:E.target.value})}),c.jsx("textarea",{value:a.argsText,placeholder:m("args-lines"),onChange:E=>u({argsText:E.target.value})}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:a.rc,onChange:E=>u({rc:E.target.checked})})," remote control"]}),c.jsxs("select",{value:a.promptMode,onChange:E=>u({promptMode:E.target.value}),children:[c.jsx("option",{value:"send-keys",children:"send-keys"}),c.jsx("option",{value:"flag",children:"flag"})]}),a.promptMode==="flag"&&c.jsx("input",{value:a.promptFlag,placeholder:"prompt flag",onChange:E=>u({promptFlag:E.target.value})}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:a.modelFlag,placeholder:"model flag",onChange:E=>u({modelFlag:E.target.value})}),c.jsx("input",{value:a.modelValue,placeholder:"model default",onChange:E=>u({modelValue:E.target.value})})]}),c.jsx("small",{children:m("env-write-only")}),h.map((E,F)=>c.jsxs("div",{className:"nc-fleet-env",children:[c.jsx("input",{value:E.key,disabled:E.configured,placeholder:"ENV_KEY",onChange:O=>{const K=h.slice();K[F]={...E,key:O.target.value},u({envRows:K})}}),c.jsx("input",{type:"password",value:E.value,placeholder:E.configured?"•••••• (unchanged)":"value",onChange:O=>{const K=h.slice();K[F]={...E,value:O.target.value},u({envRows:K})}}),c.jsx("button",{className:"nc-btn danger",onClick:()=>u({envRows:h.filter((O,K)=>K!==F)}),children:"×"})]},`${E.key}-${F}`)),c.jsx("button",{className:"nc-btn ghost",onClick:()=>u({envRows:[...h,{key:"",value:"",configured:!1}]}),children:"+ env"})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>t(null),children:m("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:n||!a.id||k||a.kind==="custom"&&!a.command||a.kind==="managed"&&(v==null?void 0:v.requiresModel)&&!a.managedModel||a.kind==="managed"&&(v==null?void 0:v.credentialEnv)===!0&&!a.envKey||a.kind==="managed"&&a.provider==="custom"&&(!a.displayName||!a.baseUrl||!a.envKey||!a.providerId),onClick:s,children:m("save")})]})]})}function ej({token:e,route:t,targets:n=[],location:s,setLocation:o,state:a,setState:u,engines:h,busy:p,onSave:f}){var Y;const[v,S]=j.useState(null),[y,b]=j.useState(""),w=a.form,k=z=>u({...a,form:{...w,...z}}),C=h.find(z=>z.id===w.engine),R=((Y=C==null?void 0:C.managed)==null?void 0:Y.client)==="shell",E=z=>{var Q,B,M,N;const Z=h.find(T=>T.id===z),G={...w.commands||{}};((Q=C==null?void 0:C.managed)==null?void 0:Q.client)==="shell"&&(w.command?G[w.engine]=w.command:delete G[w.engine]),k({engine:z,model:((B=w.models)==null?void 0:B[z])||((M=Z==null?void 0:Z.managed)==null?void 0:M.model)||((N=Z==null?void 0:Z.model)==null?void 0:N.value)||"",commands:G,command:G[z]||""})},F=z=>{const Z={...w.commands||{}};z?Z[w.engine]=z:delete Z[w.engine],k({command:z,commands:Z})},O=R&&/[|&;<>()`$*?{}\[\]~]/.test(w.command||""),K=async z=>{try{const Z=await Qh(e,z,t);S(Z),k({cwd:Z.path}),b("")}catch(Z){b(String(Z.message||Z))}};return c.jsxs("div",{className:"nc-set-form nc-fleet-form",children:[c.jsx("b",{children:a.mode==="new"?m("fleet-new-cell"):`${m("edit")} ${w.id}`}),a.mode==="new"&&c.jsxs("label",{className:"nc-field",children:[m("location"),c.jsx("span",{className:"nc-req",children:" *"}),c.jsxs("select",{value:s,onChange:z=>{o(z.target.value),k({engine:""})},children:[c.jsx("option",{value:"",children:m("local")}),n.map(z=>c.jsxs("option",{value:z.route.join("/"),disabled:z.status&&z.status!=="up",children:[z.label,z.status&&z.status!=="up"?` · ${m("node-offline")}`:""]},z.route.join("/")))]})]}),c.jsx("input",{value:w.id,disabled:a.mode!=="new",placeholder:"id",onChange:z=>k({id:z.target.value})}),c.jsx("input",{value:w.label||"",maxLength:64,placeholder:m("fleet-cell-label"),onChange:z=>k({label:z.target.value})}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:w.cwd,placeholder:m("cwd"),onChange:z=>k({cwd:z.target.value})}),c.jsx("button",{className:"nc-btn ghost",onClick:()=>v?S(null):K(w.cwd),children:m("browse")})]}),v&&c.jsxs("div",{className:"nc-fs",children:[c.jsx("div",{className:"nc-fs-path",children:v.path}),c.jsxs("div",{className:"nc-fs-list",children:[v.parent&&c.jsxs("button",{className:"nc-fs-item nc-fs-nav",onClick:()=>K(v.parent),children:["↑ ",m("fs-parent")]}),(v.dirs||[]).map(z=>c.jsxs("button",{className:"nc-fs-item",onClick:()=>K(`${v.path.replace(/\/$/,"")}/${z}`),children:["📁 ",z]},z))]})]}),y&&c.jsx("div",{className:"nc-err",children:y}),c.jsx("select",{value:w.engine,onChange:z=>E(z.target.value),children:h.map(z=>c.jsx("option",{value:z.id,children:z.label},z.id))}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:!!w.boot,onChange:z=>k({boot:z.target.checked})})," boot"]}),R?c.jsxs(c.Fragment,{children:[c.jsx("input",{value:w.command||"",maxLength:4096,placeholder:m("fleet-shell-command-placeholder"),onChange:z=>F(z.target.value)}),c.jsx("small",{children:w.command?m("fleet-shell-command-help"):m("fleet-shell-interactive")}),O&&c.jsx("small",{className:"nc-note",children:m("fleet-shell-command-metachar")})]}):c.jsxs(c.Fragment,{children:[c.jsx("input",{value:w.model||"",list:"nc-cell-models",placeholder:m("fleet-model-override"),onChange:z=>k({model:z.target.value})}),c.jsx("datalist",{id:"nc-cell-models",children:((C==null?void 0:C.availableModels)||[]).map(z=>c.jsx("option",{value:z},z))}),c.jsx("textarea",{value:w.prompt||"",placeholder:"prompt",onChange:z=>k({prompt:z.target.value})})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>u(null),children:m("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:p||!w.id||!w.cwd||!w.engine,onClick:f,children:m("save")})]})]})}function I_({token:e,route:t=[],state:n,setState:s,busy:o,onSave:a}){const[u,h]=j.useState(null),[p,f]=j.useState([]),[v,S]=j.useState(""),y=Array.isArray(t)?t.join("/"):"";j.useEffect(()=>{let R=!0;return Gh(e,y?y.split("/"):[]).then(E=>{R&&(f(E.engines||[]),S(""))}).catch(E=>{R&&(f([]),S(String(E.message||E)))}),()=>{R=!1}},[e,y]);const b=n.form,w=R=>s({...n,form:{...b,...R}}),k=async R=>{try{const E=await Qh(e,R,t);h(E),w({cwd:E.path})}catch{}},C=!b.id&&b.tmuxSession?b.tmuxSession.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32):b.id;return c.jsxs("div",{className:"nc-set-form nc-fleet-form",children:[c.jsxs("b",{children:[m("import-as-cell")," · ",b.tmuxSession]}),c.jsx("small",{children:m("import-help")}),c.jsx("input",{value:b.tmuxSession,disabled:!0,placeholder:"tmux session",readOnly:!0}),c.jsx("input",{value:b.id,placeholder:m("name"),onChange:R=>w({id:R.target.value})}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:b.cwd,placeholder:m("cwd"),onChange:R=>w({cwd:R.target.value})}),c.jsx("button",{className:"nc-btn ghost",type:"button",onClick:()=>u?h(null):k(b.cwd),children:m("browse")})]}),u&&c.jsxs("div",{className:"nc-fs",children:[c.jsx("div",{className:"nc-fs-path",children:u.path}),c.jsxs("div",{className:"nc-fs-list",children:[u.parent&&c.jsxs("button",{className:"nc-fs-item nc-fs-nav",onClick:()=>k(u.parent),children:["↑ ",m("fs-parent")]}),(u.dirs||[]).map(R=>c.jsxs("button",{className:"nc-fs-item",onClick:()=>k(`${u.path.replace(/\/$/,"")}/${R}`),children:["📁 ",R]},R))]})]}),c.jsxs("select",{value:b.engine,onChange:R=>w({engine:R.target.value}),children:[c.jsx("option",{value:"",children:m("import-engine-required")}),p.map(R=>c.jsx("option",{value:R.id,children:R.label},R.id))]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:!!b.boot,onChange:R=>w({boot:R.target.checked})})," boot"]}),(n.err||v)&&c.jsx("div",{className:"nc-err",children:n.err||v}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>s(null),children:m("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:o||!b.tmuxSession||!b.engine||!p.length,onClick:a,children:m(C?"import-as-cell":"save")})]})]})}function El(e){const t=Ks(e);return t===null?`~/${e}`:t?`~/${t}`:"~"}function tj(e){if(!e||typeof e.path!="string"||typeof e.home!="string")return"";const{path:t,home:n}=e;return!n||t===n||!t.startsWith(n+"/")?"":t.slice(n.length+1).replace(/\/+$/,"")}function nj({token:e,route:t,cell:n,busy:s=!1,onSaved:o,onClose:a}){const[u,h]=j.useState(""),[p,f]=j.useState(null),[v,S]=j.useState(!1),[y,b]=j.useState(""),w=typeof(n==null?void 0:n.cwdSuggestion)=="string"&&Ks(n.cwdSuggestion)!==null?Ks(n.cwdSuggestion):null,[k,C]=j.useState(w),R=n==null?void 0:n.id,E=s||v,F=async G=>{const Q=Ks(G);if(Q===null){f({rel:"",dirs:[],loading:!1,err:m("fleet-cwd-repair-invalid")});return}f({rel:Q,dirs:[],loading:!0,err:""});try{const B=await Qh(e,Q,t),M=tj(B);f({rel:M,dirs:Array.isArray(B.dirs)?B.dirs:[],loading:!1,err:""})}catch(B){f({rel:Q,dirs:[],loading:!1,err:String(B&&B.message||B)})}},O=G=>{if(!p)return;const Q=p.rel?`${p.rel}/${G}`:G;F(Q)},K=()=>{p!=null&&p.rel&&F(p.rel.split("/").slice(0,-1).join("/"))},Y=()=>{p&&(h(p.rel||""),f(null),b(""),C(null))},z=()=>{k!==null&&(h(k),C(null),b(""))},Z=async()=>{if(E)return;const G=Ks(u);if(G===null){b(m("fleet-cwd-repair-invalid"));return}const Q=El(G);let B=!0;try{B=window.confirm(m("fleet-cwd-repair-confirm").replace("{id}",String(R||"")).replace("{path}",Q))}catch{B=!1}if(B){S(!0),b(""),C(null);try{const M=await Q_(e,R,{cwdRel:G},t);o==null||o(M)}catch(M){const N=M&&M.data||{};if(N.code==="unportable-cwd"){b(m("fleet-cwd-repair-unportable"));const T=Array.isArray(N.cells)?N.cells.find($=>$&&$.id===R):null,L=T&&typeof T.suggestion=="string"?T.suggestion:null;L!==null&&Ks(L)!==null&&C(L)}else b(String(M&&M.message||M))}finally{S(!1)}}};return c.jsxs("div",{className:"nc-set-form nc-fleet-form nc-cwd-repair",children:[c.jsx("b",{children:m("fleet-cwd-repair-title").replace("{id}",String(R||""))}),c.jsx("small",{className:"nc-set-info",children:m("fleet-cwd-repair-help")}),c.jsx("small",{className:"nc-cwd-no-source",children:m("fleet-cwd-repair-no-source-path")}),c.jsxs("label",{className:"nc-field",children:[m("cwd"),c.jsxs("div",{className:"nc-cwd-rel-row",children:[c.jsx("span",{className:"nc-cwd-rel-prefix",children:"~/"}),c.jsx("input",{value:u,placeholder:m("fleet-cwd-repair-placeholder"),autoComplete:"off",spellCheck:"false",autoCapitalize:"none",onChange:G=>h(G.target.value)}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:E,onClick:()=>p?f(null):F(""),children:m(p?"close":"fleet-cwd-repair-browse")})]})]}),c.jsx("div",{className:"nc-cwd-preview",children:m("fleet-cwd-repair-preview").replace("{path}",El(u))}),p&&c.jsxs("div",{className:"nc-fs",children:[c.jsx("div",{className:"nc-fs-path",children:El(p.rel)}),c.jsxs("div",{className:"nc-fs-list",children:[p.rel&&c.jsxs("button",{type:"button",className:"nc-fs-item nc-fs-nav",onClick:K,children:["↑ ",m("fs-parent")]}),p.loading&&c.jsx("div",{className:"nc-fs-empty",children:"…"}),!p.loading&&p.dirs.length===0&&c.jsx("div",{className:"nc-fs-empty",children:m("fleet-cwd-repair-fs-empty")}),!p.loading&&p.dirs.map(G=>c.jsxs("button",{type:"button",className:"nc-fs-item",onClick:()=>O(G),children:["📁 ",G]},G))]}),p.err&&c.jsx("div",{className:"nc-err",children:p.err}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{type:"button",className:"nc-btn primary",disabled:E||p.loading||!!p.err,onClick:Y,children:m("fleet-cwd-repair-use-current")})})]}),y&&c.jsx("div",{className:"nc-err",role:"alert",children:y}),k!==null&&c.jsxs("div",{className:"nc-cwd-suggestion",role:"status",children:[c.jsx("span",{children:m("fleet-cwd-repair-suggestion")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:z,children:m("fleet-cwd-repair-use-suggestion").replace("{path}",El(k))})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:v,onClick:a,children:m("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:E,onClick:Z,children:v?"…":m("save")})]})]})}function ij({token:e,readonly:t,targets:n=[],startNewCell:s=!1,initialLocation:o=""}){const[a,u]=j.useState({engines:[],cells:[],managedCatalog:[]}),[h,p]=j.useState({available:!1,capabilities:[]}),[f,v]=j.useState(!1),[S,y]=j.useState(null),[b,w]=j.useState(null),[k,C]=j.useState(!1),[R,E]=j.useState(""),[F,O]=j.useState(""),[K,Y]=j.useState(o),[z,Z]=j.useState(!1),[G,Q]=j.useState(null),[B,M]=j.useState(null),[N,T]=j.useState(null),[L,$]=j.useState(!1),[U,x]=j.useState([]),[I,D]=j.useState(null),[A,W]=j.useState("manage"),X=j.useRef(!1),ie=K?K.split("/"):[],le=j.useCallback(async()=>{try{const re=await ws(e,ie);p(re);try{const xe=await Zh(e,ie);Z(!!xe.readonlyDefault)}catch{Z(!1)}if(re.provider==="builtin"&&(re.capabilities||[]).includes("definitions")){const xe=await Gh(e,ie),Oe=new Map((re.engines||[]).map(Fe=>[Fe.id,Fe]));if(xe.engines=(xe.engines||[]).map(Fe=>{var de;return{...Fe,availableModels:((de=Oe.get(Fe.id))==null?void 0:de.models)||[]}}),u(xe),(re.capabilities||[]).includes("credentials"))try{x((await f0(e,ie)).credentials||[])}catch{x([])}else x([])}E("")}catch(re){E(String(re.message||re))}finally{v(!0)}},[e,K]);j.useEffect(()=>{le();const re=setInterval(le,5e3);return()=>clearInterval(re)},[le]);const fe=new Set((h.cells||[]).filter(re=>re.active).map(re=>re.cell)),we=h.provider==="builtin"&&(h.capabilities||[]).includes("edit"),pe=(h.capabilities||[]).includes("restore");j.useEffect(()=>{!s||X.current||!we||!a.engines.length||(X.current=!0,w({mode:"new",form:M_(a.engines[0].id)}))},[s,we,a.engines]);const _e=async re=>{C(!0),E(""),O("");try{await re(),await le()}catch(xe){E(String(xe.message||xe))}C(!1)},He=()=>_e(async()=>{var me;const re=S.mode==="new",xe=S.form,Oe=a.managedCatalog||[],Fe=xe.kind==="managed"?Zo(Oe,xe):null,de=typeof(Fe==null?void 0:Fe.credentialEnv)=="string"?Fe.credentialEnv:"",ze=de&&xe.credentialValue||"",q=Q2(xe,re,Oe);!re&&S.form.kind==="custom"&&!S.form.modelFlag&&(q.model=null),!re&&S.form.kind==="custom"&&S.form.promptMode!=="flag"&&(q.promptFlag=null);let te;if(re)te=await m0(e,q,ie);else{const Te=S.original,ve=new Set(S.form.envRows.filter(ot=>!ot.remove).map(ot=>ot.key)),Ce=(Te.envKeys||[]).filter(ot=>!ve.has(ot)),nt=Object.fromEntries(S.form.envRows.filter(ot=>!ot.remove&&ot.key&&(!ot.configured||ot.value!=="")).map(ot=>[ot.key,ot.value]));te=await g0(e,Te.id,q,S.form.kind==="custom"?{set:nt,remove:Ce}:void 0,ie)}let H=null;if(de&&ze)try{H=await yg(e,de,ze,ie),x(H.credentials||[])}catch{throw await le(),re&&y({mode:"edit",original:{...q,id:xe.id},form:{...xe,credentialReveal:!1}}),new Error(m(re?"fleet-key-partial-create":"fleet-key-partial-edit"))}await le(),y(null),O(m("fleet-saved"));const ae=((me=((H==null?void 0:H.credentials)||[]).find(Te=>Te.envKey===de))==null?void 0:me.activeCells)||[],ce=[...new Set([...(te==null?void 0:te.activeCells)||[],...ae])];if(ce.length&&window.confirm(m("fleet-restart-confirm").replace("{cells}",ce.join(", "))))for(const Te of ce)await Fo(e,Te,ie)}),ht=()=>_e(async()=>{var H,ae,ce,me,Te,ve;const re=b.mode==="new",xe=b.form,Oe={...re?{id:xe.id}:{},cwd:xe.cwd,engine:xe.engine,boot:!!xe.boot},Fe={...xe.commands||{}},de=a.engines.find(Ce=>Ce.id===xe.engine);((H=de==null?void 0:de.managed)==null?void 0:H.client)==="shell"&&typeof xe.command=="string"&&xe.command.length?Fe[xe.engine]=xe.command:((ae=de==null?void 0:de.managed)==null?void 0:ae.client)==="shell"&&delete Fe[xe.engine],(Object.keys(Fe).length||!re)&&(Oe.commands=Fe);const ze=(xe.label||"").trim();re?ze&&(Oe.label=ze):Oe.label=ze||null,re?(((ce=de==null?void 0:de.managed)==null?void 0:ce.client)!=="shell"&&xe.model&&(Oe.model=xe.model),((me=de==null?void 0:de.managed)==null?void 0:me.client)!=="shell"&&xe.prompt&&(Oe.prompt=xe.prompt)):(Oe.model=((Te=de==null?void 0:de.managed)==null?void 0:Te.client)==="shell"?null:xe.model||null,((ve=de==null?void 0:de.managed)==null?void 0:ve.client)!=="shell"&&(Oe.prompt=xe.prompt||null));const q=re?await _0(e,Oe,ie):await Q_(e,b.original.id,Oe,ie),te=re?xe.id:b.original.id;w(null),O(m("fleet-saved")),!re&&(q!=null&&q.active)&&window.confirm(m("fleet-restart-confirm").replace("{cells}",te))&&await Fo(e,te,ie)}),Ye=t||z,Ae=re=>U.find(xe=>{var Oe;return(Oe=xe.engines)==null?void 0:Oe.includes(re.id)})||null,Ne=()=>_e(async()=>{var Oe;const re=await yg(e,I.envKey,I.value,ie);x(re.credentials||[]),D(null),O(m("fleet-credential-saved"));const xe=((Oe=(re.credentials||[]).find(Fe=>Fe.envKey===I.envKey))==null?void 0:Oe.activeCells)||[];if(xe.length&&window.confirm(m("fleet-restart-confirm").replace("{cells}",xe.join(", "))))for(const Fe of xe)await Fo(e,Fe,ie)}),ut=re=>_e(async()=>{if(!window.confirm(m("fleet-credential-remove-confirm").replace("{key}",re.envKey)))return;const xe=await p0(e,re.envKey,ie);x(xe.credentials||[]),O(m("fleet-credential-removed"))}),Pe=re=>Q({...re,route:Array.isArray(re==null?void 0:re.route)?re.route:ie}),Ut=async re=>{if(!G)return;const xe=G.cell||G.id,Oe=Array.isArray(G.route)?G.route:ie;if(re.action==="up"){const Fe=await Yh(e,{cell:xe,boot:!!re.boot,...re.engine?{engine:re.engine}:{},...re.model!==void 0?{model:re.model}:{},...re.permissionPolicy?{permissionPolicy:re.permissionPolicy}:{}},Oe),de=uf(Fe);de&&O(de.text)}else await Il(e,{cell:xe,boot:!!re.boot},Oe)},Rt=(re,xe)=>{E(""),M({mode:"new",route:Array.isArray(xe)?xe:ie,form:{tmuxSession:re.name,id:"",engine:"",cwd:"",boot:!1},err:""})},Dt=()=>_e(async()=>{const re=B.form;if(!re.engine){M({...B,err:m("import-engine-required")});return}await y0(e,{tmuxSession:re.tmuxSession,id:re.id||void 0,engine:re.engine,cwd:re.cwd||void 0,boot:!!re.boot},re.route||ie),M(null),O(m("fleet-saved"))}),vt=({engineRows:re=[],cellRows:xe=[]})=>_e(async()=>{const Oe=re.filter(me=>me.selected),Fe=Oe.map(me=>ib(me.engine));if(Fe.some(me=>!me))throw new Error(m("fleet-backup-invalid-engine"));const de=Oe.filter(me=>me.exists).map(me=>me.engine.id);if(de.length&&!window.confirm(m("fleet-backup-confirm-engine-overwrite").replace("{engines}",de.join(", "))))return;const ze=[...new Set([...a.engines.map(me=>me.id),...Fe.map(me=>me.id)])],q=[];for(const me of xe.filter(Te=>Te.selected)){const Te=V2(me.cell,me.engine,ze);if(!Te)throw new Error(`${me.cell.id}: ${m("fleet-backup-engine-missing")}`);q.push(Te)}const te=xe.filter(me=>me.selected&&me.exists).map(me=>me.cell.id);if(te.length&&!window.confirm(m("fleet-backup-confirm-overwrite").replace("{cells}",te.join(", "))))return;const H=Fe.length?await b0(e,Fe,de.length>0,ie):{needsRestart:[]},ae=q.length?await w0(e,q,ie):{needsRestart:[]},ce=[...new Set([...H.needsRestart||[],...ae.needsRestart||[]])];$(!1),O(`${m("fleet-backup-restored").replace("{n}",String(q.length))}${Fe.length?` · ${Fe.length} ${m("fleet-engines").toLowerCase()}`:""}${ce.length?` · ${m("fleet-backup-needs-restart").replace("{cells}",ce.join(", "))}`:""}`)}),ft=c.jsxs("label",{className:"nc-field",children:[m("location"),c.jsxs("select",{value:K,onChange:re=>{Y(re.target.value),y(null),w(null),E(""),O(""),Z(!1),v(!1),p({available:!1,capabilities:[]}),u({engines:[],cells:[],managedCatalog:[]}),x([]),D(null)},children:[c.jsx("option",{value:"",children:m("local")}),n.map(re=>c.jsxs("option",{value:re.route.join("/"),disabled:re.status&&re.status!=="up",children:[re.label,re.status&&re.status!=="up"?` · ${m("node-offline")}`:""]},re.route.join("/")))]})]}),dt=c.jsxs("div",{className:"nc-fleet-view-tabs",role:"tablist","aria-label":m("tab-fleet"),children:[c.jsx("button",{type:"button",role:"tab","aria-selected":A==="manage",className:`nc-set-tabbtn${A==="manage"?" on":""}`,onClick:()=>W("manage"),children:m("fleet-manage-location")}),c.jsx("button",{type:"button",role:"tab","aria-selected":A==="overview",className:`nc-set-tabbtn${A==="overview"?" on":""}`,onClick:()=>W("overview"),children:m("fleet-network-overview")})]});return f?we?c.jsxs("div",{className:"nc-set-tab nc-fleet-editor",children:[dt,A==="overview"?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:m("fleet-overview-help")}),c.jsx(zd,{token:e,targets:n,readonly:t,onPower:Pe,onImport:Rt})]}):c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:m("fleet-manage-help")}),ft,c.jsxs("div",{className:"nc-set-row nc-fleet-backup-actions",children:[c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>{E(""),$(!0)},children:m("fleet-backup")}),c.jsx("small",{children:m("fleet-backup-help")})]}),c.jsxs("div",{className:"nc-fleet-section-head",children:[c.jsx("b",{children:m("fleet-cells")}),c.jsx("span",{className:"nc-fleet-head-actions",children:c.jsxs("button",{className:"nc-btn primary",disabled:Ye||k||!a.engines.length,onClick:()=>{var re;E(""),w({mode:"new",form:M_((re=a.engines[0])==null?void 0:re.id)})},children:["+ ",m("add")]})})]}),a.cells.map(re=>{const xe=fe.has(re.id),Oe=h.capabilities||[],Fe=re.needsRepair===!0;return c.jsxs("div",{className:"nc-fleet-item",children:[c.jsxs("span",{children:[c.jsx("b",{children:re.id}),re.label?c.jsxs("small",{className:"nc-fleet-label",children:[" ",re.label]}):null,c.jsxs("small",{children:[re.engine," · ",Fe?c.jsx("span",{className:"nc-fleet-tag nc-fleet-tag-warn",children:m("fleet-cwd-needs-repair")}):re.cwd,xe?` · ${m("service-active")}`:` · ${m("cell-off")}`]})]}),c.jsxs("span",{children:[xe&&Oe.includes("down")&&c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>_e(()=>Il(e,{cell:re.id},ie)),children:m("stop")}),!xe&&Oe.includes("up")&&c.jsx("button",{className:"nc-btn primary",disabled:Ye||k,onClick:()=>Pe({cell:re.id,id:re.id,engine:re.engine,model:re.model,models:re.models,permissionPolicies:re.permissionPolicies,active:!1,boot:re.boot}),children:m("start")}),xe&&Oe.includes("restart")&&c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>_e(()=>Fo(e,re.id,ie)),children:m("restart")}),Fe?c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>{E(""),T(re)},children:m("fleet-cwd-repair")}):c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>{var de;E(""),w({mode:"edit",original:re,form:{...re,commands:{...re.commands||{}},command:((de=re.commands)==null?void 0:de[re.engine])||""}})},children:m("edit")}),c.jsx("button",{className:"nc-btn danger",disabled:Ye||k,onClick:()=>_e(async()=>{window.confirm(m("fleet-remove-cell").replace("{id}",re.id))&&await J_(e,re.id,!0,ie)}),children:"×"})]})]},re.id)}),c.jsxs("div",{className:"nc-fleet-section-head",children:[c.jsx("b",{children:m("fleet-engines")}),c.jsxs("button",{className:"nc-btn primary",disabled:Ye||k,onClick:()=>{E(""),y({mode:"new",form:Y2()})},children:["+ ",m("add")]})]}),a.engines.map(re=>{var Oe,Fe;const xe=Ae(re);return c.jsxs("div",{className:"nc-fleet-item",children:[c.jsxs("span",{children:[c.jsx("b",{children:re.label}),c.jsx("small",{children:re.managed?`${re.id} · ${re.managed.client} / ${re.managed.provider} · ${(Oe=re.managedInfo)!=null&&Oe.configured?m("fleet-ready"):((Fe=re.managedInfo)==null?void 0:Fe.reason)||m("fleet-not-ready")}`:`${re.id} · ${re.command}`}),xe&&c.jsxs("small",{children:[xe.envKey," · ",m(`fleet-credential-source-${xe.source||"missing"}`)]})]}),c.jsxs("span",{children:[xe&&c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>{E(""),D({envKey:xe.envKey,value:""})},children:xe.configured?m("fleet-credential-change"):m("fleet-credential-set")}),(xe==null?void 0:xe.source)==="local"&&c.jsx("button",{className:"nc-btn danger",disabled:Ye||k,onClick:()=>ut(xe),children:m("fleet-credential-forget")}),c.jsx("button",{className:"nc-btn ghost",disabled:Ye||k,onClick:()=>{E(""),y({mode:"edit",original:re,form:X2(re)})},children:m("edit")}),c.jsx("button",{className:"nc-btn danger",disabled:Ye||k,onClick:()=>_e(async()=>{window.confirm(m("fleet-remove-engine").replace("{id}",re.id))&&await v0(e,re.id,ie)}),children:"×"})]})]},re.id)})]}),S&&c.jsx($s,{onClose:()=>y(null),label:m("fleet-new-engine"),error:R,children:c.jsx(Z2,{state:S,setState:y,busy:k,onSave:He,catalog:a.managedCatalog||[]})}),b&&c.jsx($s,{onClose:()=>w(null),label:m("fleet-new-cell"),error:R,children:c.jsx(ej,{token:e,route:ie,targets:n,location:K,setLocation:Y,state:b,setState:w,engines:a.engines,busy:k,onSave:ht})}),N&&c.jsx($s,{onClose:()=>T(null),label:m("fleet-cwd-repair-title").replace("{id}",N.id),error:"",children:c.jsx(nj,{token:e,route:ie,cell:N,busy:k,onSaved:async()=>{T(null),O(m("fleet-cwd-repaired")),await le()},onClose:()=>T(null)})}),F&&c.jsx("div",{className:"nc-set-note",children:F}),R&&c.jsx("div",{className:"nc-err",children:R}),L&&c.jsx($s,{onClose:()=>$(!1),label:m("fleet-backup"),error:R,children:c.jsx(J2,{cells:a.cells,engines:a.engines,busy:k,canRestore:pe,onRestore:vt,onClose:()=>$(!1)})}),I&&c.jsx($s,{onClose:()=>D(null),label:m("fleet-credential-title"),error:R,children:c.jsxs("div",{className:"nc-fleet-form nc-credential-form",children:[c.jsx("b",{children:m("fleet-credential-title")}),c.jsx("small",{children:m("fleet-credential-help")}),c.jsxs("label",{className:"nc-field",children:[I.envKey,c.jsx("input",{type:"password",autoComplete:"off",spellCheck:"false",autoCapitalize:"none",value:I.value,onChange:re=>D({...I,value:re.target.value})})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>D(null),children:m("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:k||!I.value,onClick:Ne,children:m("save")})]})]})}),B&&c.jsx($s,{onClose:()=>M(null),label:m("import-as-cell"),error:R,children:c.jsx(I_,{token:e,route:B.route||ie,state:B,setState:M,busy:k,onSave:Dt})}),G&&c.jsx(Bl,{cell:G,token:e,route:Array.isArray(G.route)?G.route:ie,onConfirm:async re=>{try{await Ut(re)}finally{await le()}},onClose:()=>Q(null)})]}):c.jsxs("div",{className:"nc-set-tab",children:[dt,A==="overview"?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:m("fleet-overview-help")}),c.jsx(zd,{token:e,targets:n,readonly:t,onPower:Pe,onImport:Rt})]}):c.jsxs(c.Fragment,{children:[ft,c.jsxs("div",{className:"nc-set-info",children:[m(R?"fleet-editor-load-error":"fleet-editor-unavailable"),!R&&h.reason?` ${h.reason}`:""]})]}),R&&c.jsx("div",{className:"nc-err",children:R}),B&&c.jsx($s,{onClose:()=>M(null),label:m("import-as-cell"),error:R,children:c.jsx(I_,{token:e,route:B.route||ie,state:B,setState:M,busy:k,onSave:Dt})}),G&&c.jsx(Bl,{cell:G,token:e,route:Array.isArray(G.route)?G.route:ie,onConfirm:async re=>{try{await Ut(re)}finally{await le()}},onClose:()=>Q(null)})]}):c.jsxs("div",{className:"nc-set-tab",children:[dt,A==="overview"?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:m("fleet-overview-help")}),c.jsx(zd,{token:e,targets:n,readonly:t,onPower:Pe,onImport:Rt})]}):c.jsxs(c.Fragment,{children:[ft,c.jsx("div",{className:"nc-set-info",children:m("fleet-editor-loading")})]})]})}const Of="nc_notification_speech_v1",Nl="nc-notification-speech",Fh="nc-notification-speech-preview",sj=320,rj=2,oj=3e4,aj=1e4,lj=6e4,cj=64,O_=Object.freeze({enabled:!1}),uj=Object.freeze({it:"it-IT",en:"en-US",es:"es-ES"}),dj=Object.freeze({it:new Set(["che","con","correzione","della","delle","difetto","il","lavoro","nessun","non","per","pronto","pubblicata","pubblicato","rilascio","sono","stato","tutti","verificata","verificato","versione","completata","completato","riepilogo"]),en:new Set(["all","and","completed","fix","for","issue","no","published","ready","release","summary","the","verified","version","with","work"]),es:new Set(["con","correccion","defecto","el","esta","para","publicada","publicado","resumen","sin","todos","trabajo","verificada","verificado","version","y"])}),Bf=new WeakSet;function zf(e){return{enabled:(e&&typeof e=="object"&&!Array.isArray(e)?e:{}).enabled===!0}}function rb(){try{return typeof localStorage<"u"?localStorage:null}catch{return null}}function B_(e=rb()){if(!e)return{...O_};try{return zf(JSON.parse(e.getItem(Of)||"null"))}catch{return{...O_}}}function hj(e,t=rb()){const n=zf(e);if(t)try{t.setItem(Of,JSON.stringify(n))}catch{}return n}function Ur(){try{return typeof window<"u"?window:null}catch{return null}}function ea(e=Ur()){try{return!!(e&&e.speechSynthesis&&typeof e.speechSynthesis.speak=="function"&&typeof e.SpeechSynthesisUtterance=="function")}catch{return!1}}function ob(e=Ur()){return!!(e&&(typeof e=="object"||typeof e=="function")&&Bf.has(e))}function $h(e=Ur()){e&&(typeof e=="object"||typeof e=="function")&&Bf.delete(e)}function Ar(e){const t=String(e||"").trim().toLowerCase().split("-")[0];return uj[t]||""}function cc(e){return String(e||"").normalize("NFC").replace(/[\p{Cc}\p{Cf}]+/gu," ").replace(/\s+/g," ").trim()}function fj(e){const t=cc(e).normalize("NFD").replace(new RegExp("\\p{M}+","gu"),"").toLowerCase(),n=new Set(t.match(new RegExp("\\p{L}+","gu"))||[]);if(n.size<4)return"";const s=Object.entries(dj).map(([o,a])=>({lang:o,score:[...n].reduce((u,h)=>u+(a.has(h)?1:0),0)})).sort((o,a)=>a.score-o.score);return s[0].score>=3&&s[0].score-s[1].score>=2?s[0].lang:""}function pj(e,t){const n=Ar(e&&e.lang);return n||Ar(fj(e&&e.body))||Ar(t)}function ab(e){try{const t=e&&e.speechSynthesis&&e.speechSynthesis.getVoices;return typeof t=="function"?Array.from(t.call(e.speechSynthesis)||[]).filter(Boolean):[]}catch{return[]}}function lb(e,t){const n=Ar(t);if(!n)return null;const s=Array.from(e||[]).filter(h=>h&&typeof h.lang=="string"&&h.lang.trim()),o=s.filter(h=>h.lang.trim().toLowerCase()===n.toLowerCase()),a=n.slice(0,2).toLowerCase(),u=s.filter(h=>h.lang.trim().toLowerCase().split("-")[0]===a);return o.find(h=>h.default===!0)||o[0]||u.find(h=>h.default===!0)||u[0]||null}function z_(e){return cc(e).replace(/\bBearer\s+\S+/gi,"Bearer [redacted]").replace(/\b(?:sk|hf|zai|xox[baprs]|gh[pousr]|npm)[_-][A-Za-z0-9._-]{8,}\b/gi,"[redacted]").replace(/\b(?:token|password|secret|credential|api[_-]?key)\s*[=:]\s*(?:"[^"]*"|'[^']*'|\S+)/gi,"[redacted]").replace(/\b[A-Z][A-Z0-9_]{2,}\s*=\s*(?:"[^"]*"|'[^']*'|\S+)/g,"[redacted]").replace(/(?:\/home\/[^/\s]+|\/Users\/[^/\s]+|\/data\/(?:data|user\/\d+)\/[^/\s]+)(?:\/[^\s]*)?/g,"[private path]").replace(/\b[A-Za-z]:\\Users\\[^\\\s]+(?:\\[^\s]*)?/g,"[private path]")}function mj(e,t=sj){const n=z_(e&&e.title),s=z_(e&&e.body),o=[n,s&&s!==n?s:""].filter(Boolean).join(". "),a=Array.from(o);return a.length<=t?o:`${a.slice(0,Math.max(0,t-1)).join("").trimEnd()}…`}function gj(e){try{return!!(e&&e.visibilityState==="visible"&&typeof e.hasFocus=="function"&&e.hasFocus())}catch{return!1}}function cb(e=Ur()){if(!ea(e))return!1;try{return e.speechSynthesis.cancel(),!0}catch{return!1}}function vj(e){try{e&&typeof e.dispatchEvent=="function"&&typeof e.Event=="function"&&e.dispatchEvent(new e.Event(Fh))}catch{}}function _j(e,t,n=Ur(),s={}){var f;if($h(n),!ea(n))return Promise.resolve(!1);const o=cc(e);if(!o||(f=s.signal)!=null&&f.aborted)return Promise.resolve(!1);const a=Number.isFinite(s.timeoutMs)&&s.timeoutMs>0?s.timeoutMs:aj,u=s.setTimer||((v,S)=>setTimeout(v,S)),h=s.clearTimer||(v=>clearTimeout(v)),p=s.signal;return vj(n),new Promise(v=>{let S=!1,y=!1,b=null,w=null;const k=C=>{if(!S){if(S=!0,p&&w&&p.removeEventListener("abort",w),b!==null&&h(b),C)Bf.add(n);else try{n.speechSynthesis.cancel()}catch{}v(C)}};w=()=>k(!1),p&&p.addEventListener("abort",w,{once:!0});try{n.speechSynthesis.cancel();const C=new n.SpeechSynthesisUtterance(o);C.lang=Ar(t);const R=lb(ab(n),C.lang);R&&(C.voice=R),C.onstart=()=>{y=!0},C.onend=()=>k(y),C.onerror=()=>k(!1),b=u(()=>k(!1),a),n.speechSynthesis.speak(C)}catch{k(!1)}})}function yj(e={}){const t=e.scope||Ur(),n=e.document||t&&t.document,s=Number.isInteger(e.maxPending)&&e.maxPending>=0?e.maxPending:rj,o=Number.isFinite(e.watchdogMs)&&e.watchdogMs>0?e.watchdogMs:oj,a=Number.isFinite(e.dedupWindowMs)&&e.dedupWindowMs>0?e.dedupWindowMs:lj,u=Number.isInteger(e.maxDedup)&&e.maxDedup>0?e.maxDedup:cj,h=e.now||Date.now,p=e.setTimer||((B,M)=>setTimeout(B,M)),f=e.clearTimer||(B=>clearTimeout(B)),v=e.isPrimed||(()=>ob(t)),S=t&&t.speechSynthesis;let y=null,b=null,w=[],k=[],C=()=>{};const R=new Map,E=()=>{k=ab(t)};if(E(),S&&typeof S.addEventListener=="function")S.addEventListener("voiceschanged",E),C=()=>S.removeEventListener("voiceschanged",E);else if(S&&"onvoiceschanged"in S){const B=S.onvoiceschanged,M=(...N)=>{E(),typeof B=="function"&&B.apply(S,N)};S.onvoiceschanged=M,C=()=>{S.onvoiceschanged===M&&(S.onvoiceschanged=B)}}const F=B=>{if(B&&typeof B.id=="string"&&B.id)return`id:${B.id}`;const M=Number(B&&B.ts),N=cc(B&&B.title);return Number.isFinite(M)&&N?`frame:${M}:${N}`:""},O=B=>{if(!B)return!0;const M=h();for(const[N,T]of R)M-T>=a&&R.delete(N);if(R.has(B))return!1;for(R.set(B,M);R.size>u;)R.delete(R.keys().next().value);return!0},K=()=>{b!==null&&f(b),b=null},Y=()=>{K(),y&&(y.onend=null,y.onerror=null,y=null),cb(t)},z=B=>{if(!ea(t))return w=[],"unsupported";let M;try{M=new t.SpeechSynthesisUtterance(B.text),M.lang=Ar(B.lang);const T=lb(k,M.lang);T&&(M.voice=T)}catch{return w=[],"error"}y=M;const N=()=>{if(y!==M)return;K(),y=null,M.onend=null,M.onerror=null;const T=w.shift();T&&z(T)};M.onend=N,M.onerror=N;try{return b=p(()=>{if(y!==M)return;y=null,M.onend=null,M.onerror=null;try{t.speechSynthesis.cancel()}catch{}K();const T=w.shift();T&&z(T)},o),t.speechSynthesis.speak(M),"speaking"}catch{return N(),"error"}},Z=(B,M)=>{if(!ea(t))return"unsupported";const N=mj(B);if(!N)return"empty";if(!O(F(B)))return"duplicate";if(!gj(n))return"inactive";if(!v())return"unprimed";const T={text:N,lang:M};return B&&B.urgency==="high"?(w=[],Y(),z(T)):y?s===0?"dropped":(w.length>=s&&w.shift(),w.push(T),"queued"):z(T)},G=()=>{w=[],Y()};return{enqueue:Z,stop:G,dispose:()=>{G(),C(),C=()=>{}}}}function ub(){const[e,t]=j.useState(B_);j.useEffect(()=>{const s=o=>{if((o==null?void 0:o.type)==="storage"&&o.key!==Of&&o.key!==null)return;const a=(o==null?void 0:o.type)===Nl?zf(o.detail):B_();t(a)};return window.addEventListener(Nl,s),window.addEventListener("storage",s),()=>{window.removeEventListener(Nl,s),window.removeEventListener("storage",s)}},[]);const n=j.useCallback(s=>{const o=hj({enabled:s===!0});return t(o),window.dispatchEvent(new CustomEvent(Nl,{detail:o})),o.enabled},[]);return[e.enabled,n]}function wj({text:e}){const[t,n]=j.useState(!1),s=async()=>{try{await navigator.clipboard.writeText(e),n(!0),setTimeout(()=>n(!1),1500)}catch{}};return c.jsxs("div",{className:"nc-set-copyline",children:[c.jsx("code",{children:e}),c.jsxs("button",{type:"button",className:"nc-btn ghost",onClick:s,children:[c.jsx(Ze,{name:"copy",size:14})," ",m(t?"copied":"copy")]})]})}function bj({value:e}){const[t,n]=j.useState("");return j.useEffect(()=>{let s=!0;return t2.toDataURL(e,{margin:1,width:220}).then(o=>{s&&n(o)}).catch(()=>{}),()=>{s=!1}},[e]),t?c.jsx("img",{src:t,width:"220",height:"220",alt:"NexusCrew pairing QR"}):null}function Sj({token:e,nodes:t,roster:n,settings:s,readonly:o,refresh:a,refreshAliases:u,vlUnavailable:h=[]}){const[p,f]=j.useState(null),[v,S]=j.useState(null),[y,b]=j.useState(null),[w,k]=j.useState({ssh:"",sshPort:"",name:""}),[C,R]=j.useState(""),[E,F]=j.useState(""),[O,K]=j.useState(!1),[Y,z]=j.useState(null),Z=Date.now(),G=s&&s.deviceName||"",Q=(t||[]).filter(x=>x&&x.direction==="outbound"&&x.name&&x.ssh),B=Q.find(x=>x.name===C)||Q[0]||null,M=B?Yd(B.tunnel,Z):null,N=B!=null&&B.shared?M!=null&&M.up?"share-local-active":"share-local-pending":M!=null&&M.up?"share-local-private":"share-local-private-down",T=[{key:"peer-group-hubs",rows:(t||[]).filter(x=>x.kind!=="transitive"&&x.kind!=="vl"&&x.relation!=="client")},{key:"peer-group-clients",rows:(t||[]).filter(x=>x.kind!=="transitive"&&x.kind!=="vl"&&x.relation==="client")},{key:"peer-group-routed",rows:(t||[]).filter(x=>x.kind==="transitive")},{key:"peer-group-vl",rows:(t||[]).filter(x=>x.kind==="vl")}],L=Y&&(t||[]).find(x=>`${x.kind||"direct"}:${x.nodeId||x.name}`===Y)||null,$=async x=>{if(B){f(null),S(`${B.name}:share`);try{const I=await A0(e,B.name,x);await a(),I&&I.reversePoolPending===!0&&f(`${B.name}: ${m("share-reverse-pool-pending")}`)}catch(I){const D=I!=null&&I.data&&I.data.shared===!1?I.data:null;if(D)try{await a()}catch{}const A=I!=null&&I.data&&typeof I.data.hint=="string"?I.data.hint:"",W=(D==null?void 0:D.revoked)===!0&&(D==null?void 0:D.localReconcilePending)===!0?m("share-off-local-reconcile-pending"):(D==null?void 0:D.revoked)===!1&&(D==null?void 0:D.reconcilePending)===!0?m("share-off-hub-reconcile-pending"):"",X=I!=null&&I.data&&I.data.reversePoolPending===!0?m("share-reverse-pool-pending"):"",ie=I!=null&&I.data&&typeof I.data.code=="string"?I.data.code:"",le=I!=null&&I.data&&typeof I.data.detail=="string"?I.data.detail.trim():"",fe=le&&le!==String(I.message||"")?le:"";f(`${B.name}: ${String(I.message||I)}${ie?` [${ie}]`:""}${fe?` — ${fe}`:""}${A?` — ${A}`:""}${W?` — ${W}`:""}${X?` — ${X}`:""}`)}S(null)}},U=async()=>{f(null);const x=vs(w.name||E||G||"NexusCrew"),I=Mx({name:x,ssh:w.ssh,sshPort:w.sshPort});if(!I.ok){f(m(I.error));return}S("invite");try{b(await N0(e,{...E||G?{label:E||G}:{},name:x,ssh:I.value.ssh,...I.value.sshPort?{sshPort:I.value.sshPort}:{}}))}catch(D){f(String(D.message||D))}S(null)};return c.jsxs("div",{className:"nc-set-tab",children:[c.jsx("div",{className:"nc-sheet-label",children:m("peer-inventory-title")}),(t||[]).length===0&&c.jsx("div",{className:"nc-empty",children:m("no-nodes")}),T.map(x=>x.rows.length>0&&c.jsxs("div",{className:"nc-peer-group",children:[c.jsxs("div",{className:"nc-peer-group-title",children:[m(x.key)," ",c.jsx("span",{children:x.rows.length})]}),x.rows.map(I=>{const D=f2(I);if(!D)return null;const A=`${I.kind||"direct"}:${I.nodeId||I.name}`,W=D.routed?{up:D.reach.up,since:null}:Yd(I.tunnel,Z);return c.jsxs("button",{type:"button",className:`nc-set-node nc-node-row${D.routed?" routed":""}`,"aria-label":`${D.title} — ${m("node-detail-open")}`,onClick:()=>z(A),children:[c.jsx("span",{className:`nc-dot ${D.reach.up?"on":""}`}),c.jsxs("span",{className:"nc-node-row-text",children:[c.jsx("b",{children:D.title}),c.jsxs("small",{children:[D.routed?D.routeLabel:`${D.subtitle} · ${m(D.exposure.shortKey)}`,!D.routed&&D.exposure.shortKey==="visibility-selected"?` (${D.exposure.count})`:""]})]}),c.jsxs("span",{className:`nc-set-tunnel${D.reach.up?" up":""}`,children:[m(D.reach.key),W.since?` · ${W.since}`:""]}),c.jsx("span",{className:"nc-node-row-more",children:c.jsx(Ze,{name:"chevronDown",size:14})})]},A)})]},x.key)),h.length>0&&c.jsxs("div",{className:"nc-err",children:[m("vl-owners-unavailable"),": ",h.map(x=>x.label||x.route.join("/")).join(", ")]}),L&&c.jsx(E2,{node:L,nodes:t||[],token:e,readonly:o,refresh:a,onClose:()=>z(null)}),(n||[]).some(x=>!x.direct&&x.instanceId)&&c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:m("routed-node-aliases")}),c.jsx("small",{className:"nc-set-hint",children:m("routed-node-aliases-help")}),(n||[]).filter(x=>!x.direct&&x.instanceId).map(x=>c.jsxs("div",{className:"nc-set-node",children:[c.jsxs("div",{className:"nc-set-node-head",children:[c.jsx("span",{className:`nc-dot ${x.status==="up"?"on":""}`}),c.jsx("b",{children:x.label||x.name}),c.jsx("small",{children:(x.route||[]).join(" › ")})]}),c.jsxs("div",{className:"nc-set-node-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!v,onClick:async()=>{const I=window.prompt(m("node-local-alias"),x.alias||"");if(I!=null){if(I.trim()&&!Mr(I.normalize("NFC"))){f(m("err-label"));return}S(`${x.instanceId}:alias`),f(null);try{I.trim()?await R0(e,x.instanceId,I):await Sg(e,x.instanceId),u()}catch(D){f(String(D.message||D))}S(null)}},children:m("alias-on-device")}),x.alias&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!v,onClick:async()=>{S(`${x.instanceId}:alias-reset`),f(null);try{await Sg(e,x.instanceId),u()}catch(I){f(String(I.message||I))}S(null)},children:m("reset-alias")})]})]},x.instanceId))]}),B&&c.jsxs("div",{className:"nc-set-form nc-local-share",children:[c.jsx("div",{className:"nc-sheet-label",children:m("share-local-heading")}),Q.length>1&&c.jsxs("label",{className:"nc-field",children:[m("share-local-hub"),c.jsx("select",{value:B.name,disabled:o||!!v,onChange:x=>R(x.target.value),children:Q.map(x=>c.jsx("option",{value:x.name,children:x.label||x.name},x.name))})]}),c.jsxs("label",{className:"nc-check nc-node-share",children:[c.jsx("input",{type:"checkbox",checked:B.shared===!0,disabled:o||!!v,onChange:x=>$(x.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:m("share-local-through").replace("{device}",G||m("local")).replace("{hub}",B.label||B.name)}),c.jsx("small",{children:m(B.shared?"share-node-on-desc":"share-node-off-desc")})]})]}),c.jsx("div",{className:`nc-set-test${B.shared&&(M!=null&&M.up)?" ok":M!=null&&M.up?"":" ko"}`,children:m(N)}),!(M!=null&&M.up)&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!v,onClick:()=>$(B.shared===!0),children:m("share-local-reconnect")})]}),c.jsx(Yw,{token:e,deviceDefault:G,localNodeId:s&&s.nodeId||"",localNameDefault:s&&s.localName||"",readonly:o,onSuccess:a}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:m("invite-node")}),c.jsx("small",{className:"nc-set-hint",children:m("invite-v2-hint")}),c.jsxs("div",{className:"nc-set-info nc-invite-endpoint",children:[m("invite-target-local"),": ",c.jsx("b",{children:G||m("invite-target-this")})]}),c.jsxs("label",{className:"nc-field",children:[m("invite-endpoint-label"),c.jsx("input",{placeholder:"user@host",value:w.ssh,disabled:o,onChange:x=>k({...w,ssh:x.target.value})}),c.jsx("small",{className:"nc-set-hint",children:m("invite-endpoint-needed")})]}),Q.length>0&&c.jsx("small",{className:"nc-set-hint nc-invite-hub-hint",children:m("invite-hub-only")}),O&&c.jsxs("div",{className:"nc-invite-advanced",children:[c.jsxs("label",{className:"nc-field",children:[m("node-ssh-port-label"),c.jsx("input",{inputMode:"numeric",placeholder:"22",value:w.sshPort,disabled:o,onChange:x=>k({...w,sshPort:x.target.value.replace(/[^0-9]/g,"").slice(0,5)})})]}),c.jsxs("label",{className:"nc-field",children:[m("device-name-label"),c.jsx("input",{placeholder:G||"NexusCrew",value:E,disabled:o,onChange:x=>F(x.target.value)}),c.jsx("small",{className:"nc-set-hint",children:m("device-name-hint")})]}),c.jsxs("label",{className:"nc-field",children:[m("node-name-label"),c.jsx("input",{placeholder:vs(E||G||m("node-name-ph")),value:w.name,disabled:o,onChange:x=>k({...w,name:x.target.value?vs(x.target.value):""})})]})]}),c.jsxs("div",{className:"nc-set-row nc-invite-actions",children:[c.jsx("button",{type:"button",className:"nc-btn primary",disabled:o||!!v||!w.ssh.trim(),onClick:U,children:m("create-pairing-link")}),c.jsxs("button",{type:"button",className:"nc-btn ghost",disabled:!!v,onClick:()=>K(x=>!x),children:[O?"▾":"▸"," ",m("pair-advanced")]})]}),y&&c.jsxs(c.Fragment,{children:[c.jsx(bj,{value:y.pairingUrl}),c.jsx(wj,{text:y.pairingUrl}),c.jsx("div",{className:"nc-set-info",children:m("invite-next-steps")})]})]}),p&&c.jsx("div",{className:"nc-err",children:p})]})}function xj({token:e,readonly:t}){const[n,s]=j.useState("idle"),[o,a]=j.useState(!1),[u,h]=j.useState(null);j.useEffect(()=>{let f=!1;return j2().then(v=>{f||s(v)}),()=>{f=!0}},[]);const p=async()=>{h(null),a(!0);try{n==="subscribed"?(await P2(e),s("idle")):(await R2(e),s("subscribed"))}catch(f){const v=String(f.message||f);v==="push-denied"?(s("denied"),h(m("push-denied"))):v==="push-unsupported"?(s("unsupported"),h(m("push-unsupported"))):h(v)}a(!1)};return n==="unsupported"?c.jsx("div",{className:"nc-set-info",children:m("push-unsupported")}):c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:t||o||n==="denied",title:t?m("settings-readonly"):"",onClick:p,children:m(n==="subscribed"?"push-disable":"push-enable")}),c.jsx("span",{className:"nc-set-info",children:m(n==="subscribed"?"push-on":n==="denied"?"push-denied":"push-off")})]}),u&&c.jsx("div",{className:"nc-err",children:u})]})}function kj(){const[e,t]=ub(),[n,s]=j.useState(""),[o,a]=j.useState(""),[u,h]=j.useState(!1),[p,f]=j.useState(ob),v=j.useRef(0),S=j.useRef(!1),y=j.useRef(null),b=ea();j.useEffect(()=>()=>{var C;v.current+=1,S.current&&((C=y.current)==null||C.abort(),y.current=null,S.current=!1,$h())},[]);const w=async()=>{const C=v.current+=1,R=typeof AbortController=="function"?new AbortController:null;s(""),a(""),h(!0),f(!1),S.current=!0,y.current=R;const E=await _j(m("notification-speech-preview"),Ol(),void 0,R?{signal:R.signal}:{});return C!==v.current||(y.current=null,S.current=!1,h(!1),f(E),E?a(m("notification-speech-preview-ok")):s(m("notification-speech-preview-failed"))),E},k=C=>{var R;if(t(C),s(""),a(""),C)w();else{v.current+=1;const E=S.current;(R=y.current)==null||R.abort(),y.current=null,S.current=!1,h(!1),f(!1),$h(),E||cb()}};return c.jsxs("div",{className:"nc-set-form",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox","aria-label":m("notification-speech-enable"),checked:e,disabled:!b,onChange:C=>k(C.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:m("notification-speech-enable")}),c.jsx("small",{children:m("notification-speech-help")})]})]}),b?c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:!e||u,onClick:()=>{w()},children:m(u?"notification-speech-testing":"notification-speech-test")})}):c.jsx("div",{className:"nc-set-info",children:m("notification-speech-unsupported")}),e&&!p&&!u&&!n&&c.jsx("div",{className:"nc-set-info",children:m("notification-speech-needs-test")}),o&&c.jsx("div",{className:"nc-set-info",role:"status",children:o}),n&&c.jsx("div",{className:"nc-err",children:n})]})}function Cj({token:e,readonly:t,nodes:n=[],settings:s=null}){const[o,a]=j.useState(null),[u,h]=j.useState([]),[p,f]=j.useState({name:"",mode:"primary-failover",targets:[]}),[v,S]=j.useState(null),[y,b]=j.useState(!1),[w,k]=j.useState(null),[C,R]=j.useState(""),E=j.useCallback(async()=>{try{const[U,x]=await Promise.all([D0(e),B0(e)]);a(U),h(Array.isArray(x==null?void 0:x.groups)?x.groups:[]),R("")}catch(U){R(String(U.message||U))}},[e]);j.useEffect(()=>{E()},[E]);const F=async U=>{b(!0),k(null),R("");try{a(await M0(e,U))}catch(x){R(String(x.message||x))}b(!1)},O=async()=>{b(!0),k(null),R("");try{k({kind:"test",...await I0(e)})}catch(U){R(String(U.message||U))}b(!1)},K=async()=>{b(!0),k(null),R("");try{k({kind:"stop",...await O0(e)})}catch(U){R(String(U.message||U))}b(!1)},Y=[],z=new Set,Z=(U,x)=>{const I=typeof U=="string"?U.toLowerCase():"";!/^[a-f0-9]{32}$/.test(I)||z.has(I)||(z.add(I),Y.push({id:I,label:x||I}))};Z(s==null?void 0:s.nodeId,m("audio-group-local"));for(const U of n||[])Z((U==null?void 0:U.nodeId)||(U==null?void 0:U.instanceId),(U==null?void 0:U.label)||(U==null?void 0:U.name)||(U==null?void 0:U.nodeId));for(const U of p.targets)Z(U,`${U} (${m("audio-group-unavailable-target")})`);const G=(U=null)=>{R(""),U?(S(U.name),f({name:U.name,mode:U.mode,targets:[...U.targets||[]]})):(S(null),f({name:"",mode:"primary-failover",targets:[]}))},Q=(U,x)=>f(I=>({...I,targets:x?[...new Set([...I.targets,U])]:I.targets.filter(D=>D!==U)})),B=async()=>{const U=p.name.trim().toLowerCase();if(!/^[a-z0-9][a-z0-9-]{0,31}$/.test(U)||p.targets.length<1){R(m("audio-group-invalid"));return}b(!0),R("");try{const x=await z0(e,U,{targets:p.targets,mode:p.mode}),I=x==null?void 0:x.group;if(!I||!I.name)throw new Error(m("audio-group-save-failed"));h(D=>[...D.filter(A=>A.name!==I.name),I].sort((A,W)=>A.name.localeCompare(W.name))),G(null)}catch(x){R(String(x.message||x))}b(!1)},M=async U=>{b(!0),R("");try{await F0(e,U),h(x=>x.filter(I=>I.name!==U)),v===U&&G(null)}catch(x){R(String(x.message||x))}b(!1)},N=(o==null?void 0:o.installed)===!0,T=(o==null?void 0:o.liveness)==="ready",L=(o==null?void 0:o.consent)===!0&&N&&T,$=w?w.kind==="stop"?m(w.stopped?"audio-stop-stopped":"audio-stop-idle"):w.status==="accepted"||w.status==="spoken"?m("audio-test-accepted"):w.status==="unknown"?m("audio-test-unknown"):`${m("audio-test-refused")}${w.reason?` (${w.reason})`:""}`:"";return c.jsxs("div",{className:"nc-set-tab nc-audio-settings",children:[c.jsxs("div",{className:"nc-set-info",children:[c.jsx("b",{children:m("audio-title")}),c.jsx("br",{}),m("audio-help")]}),o?c.jsxs(c.Fragment,{children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox","aria-label":m("audio-consent"),checked:o.consent===!0,disabled:t||y,onChange:U=>{F(U.target.checked)}}),c.jsxs("span",{children:[c.jsx("b",{children:m("audio-consent")}),c.jsx("small",{children:m("audio-consent-help")})]})]}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:m("audio-capability")}),c.jsxs("div",{className:"nc-set-info",children:[m("audio-adapter"),": ",o.adapter||m("audio-adapter-none"),c.jsx("br",{}),m("audio-installed"),": ",m(N?"audio-installed-yes":"audio-installed-no"),c.jsx("br",{}),m("audio-liveness"),": ",m(T?"audio-liveness-ready":"audio-liveness-unavailable"),Array.isArray(o.languages)&&o.languages.length>0&&c.jsxs(c.Fragment,{children:[c.jsx("br",{}),m("audio-languages"),": ",o.languages.join(", ")]})]}),o.limits&&c.jsxs("small",{className:"nc-set-hint",children:[m("audio-limits"),": ",o.limits]})]}),!o.consent&&c.jsx("div",{className:"nc-set-info",children:m("audio-test-needs-consent")}),!N&&c.jsx("div",{className:"nc-set-info",children:m("audio-unavailable")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn primary",disabled:t||y||!L,title:t?m("settings-readonly"):"",onClick:()=>{O()},children:m(y?"audio-testing":"audio-test")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:y,onClick:()=>{K()},children:m(y?"audio-stopping":"audio-stop")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:y,onClick:()=>{E()},children:m("refresh")})]}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:m("audio-groups-title")}),c.jsx("small",{className:"nc-set-hint",children:m("audio-groups-help")}),u.length===0&&c.jsx("div",{className:"nc-set-info",children:m("audio-groups-empty")}),u.map(U=>{var x;return c.jsxs("div",{className:"nc-set-row",children:[c.jsxs("span",{children:[c.jsx("b",{children:U.name})," ",c.jsxs("small",{children:["· ",U.mode==="fanout"?m("audio-group-fanout"):m("audio-group-primary")," · ",((x=U.targets)==null?void 0:x.length)||0]})]}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:t||y,onClick:()=>G(U),children:m("audio-group-edit")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:t||y,onClick:()=>{M(U.name)},children:m("audio-group-delete")})]},U.name)}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:t||y,onClick:()=>G(null),children:m("audio-group-new")})}),c.jsxs("label",{className:"nc-field",children:[m("audio-group-name"),c.jsx("input",{"aria-label":m("audio-group-name"),value:p.name,maxLength:"32",disabled:t||y,onChange:U=>f(x=>({...x,name:U.target.value}))})]}),c.jsxs("label",{className:"nc-field",children:[m("audio-group-mode"),c.jsxs("select",{"aria-label":m("audio-group-mode"),value:p.mode,disabled:t||y,onChange:U=>f(x=>({...x,mode:U.target.value})),children:[c.jsx("option",{value:"primary-failover",children:m("audio-group-primary")}),c.jsx("option",{value:"fanout",children:m("audio-group-fanout")})]})]}),c.jsx("div",{className:"nc-set-info",children:m("audio-group-targets")}),Y.length===0?c.jsx("small",{className:"nc-set-hint",children:m("audio-group-no-targets")}):Y.map(U=>c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:p.targets.includes(U.id),disabled:t||y,onChange:x=>Q(U.id,x.target.checked)}),c.jsxs("span",{children:[U.label,c.jsx("small",{children:U.id})]})]},U.id)),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn primary",disabled:t||y,onClick:()=>{B()},children:m("audio-group-save")}),v&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:y,onClick:()=>G(null),children:m("audio-group-cancel")})]})]})]}):c.jsx("div",{className:"nc-set-info",children:m("audio-loading")}),$&&c.jsx("div",{className:"nc-set-note",role:"status",children:$}),C&&c.jsx("div",{className:"nc-err",children:C})]})}function Ej(){const[e,t]=kf();return c.jsxs("div",{className:"nc-set-tab nc-input-settings",children:[c.jsx("div",{className:"nc-set-info",children:m("input-settings-local")}),c.jsxs("div",{className:"nc-set-form",children:[c.jsxs("label",{className:"nc-field",children:[m("terminal-keyboard-gesture"),c.jsx("select",{"aria-label":m("terminal-keyboard-gesture"),value:e.terminalKeyboardGesture,onChange:n=>t({terminalKeyboardGesture:n.target.value}),children:Ew.map(n=>c.jsx("option",{value:n,children:m(`terminal-keyboard-${n}`)},n))}),c.jsx("small",{children:m("terminal-keyboard-gesture-help")})]}),c.jsxs("label",{className:"nc-field",children:[m("keybar-layout"),c.jsx("select",{"aria-label":m("keybar-layout"),value:e.keybarLayout,onChange:n=>t({keybarLayout:n.target.value}),children:Nw.map(n=>c.jsx("option",{value:n,children:m(`keybar-layout-${n}`)},n))}),c.jsx("small",{children:m("keybar-layout-help")})]})]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:e.keybarKeepsKeyboardClosed,onChange:n=>t({keybarKeepsKeyboardClosed:n.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:m("keybar-ime-lock")}),c.jsx("small",{children:m("keybar-ime-lock-help")})]})]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:e.voiceKeepsKeyboardClosed,onChange:n=>t({voiceKeepsKeyboardClosed:n.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:m("voice-ime-lock")}),c.jsx("small",{children:m("voice-ime-lock-help")})]})]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:e.showKeybarEnter,onChange:n=>t({showKeybarEnter:n.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:m("keybar-enter-show")}),c.jsx("small",{children:m("keybar-enter-show-help")})]})]}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>t(ps),children:m("input-settings-reset")})})]})}function Nj({token:e,roster:t=[],readonly:n}){const s=[{key:"local",route:[],label:m("diagnostics-local"),status:"up"},...t.filter(L=>Array.isArray(L.route)&&L.route.length).map(L=>({key:L.route.join("/"),route:L.route,label:L.label||L.name,status:L.status}))],[o,a]=j.useState("local"),u=s.find(L=>L.key===o)||s[0],[h,p]=j.useState(null),[f,v]=j.useState([]),[S,y]=j.useState(""),[b,w]=j.useState(!1),[k,C]=j.useState(!0),[R,E]=j.useState(900),[F,O]=j.useState("all"),[K,Y]=j.useState(""),z=j.useRef(0),Z=j.useRef(null),G=j.useCallback(async()=>{try{const L=await W0(e,u.route);if(p(L),y(""),!b){const $=await K0(e,{after:z.current,limit:200},u.route);Array.isArray($.records)&&$.records.length&&(z.current=$.cursor,v(U=>[...U,...$.records].slice(-500)))}}catch(L){y(L&&L.status===404?m("diagnostics-unsupported"):String(L.message||L))}},[e,u.key,b]);j.useEffect(()=>{z.current=0,v([]),p(null),y("")},[u.key]),j.useEffect(()=>{let L=null,$=!0;const U=()=>{!$||document.visibilityState==="hidden"||G()},x=()=>{L&&clearInterval(L),L=document.visibilityState==="hidden"?null:setInterval(U,2e3),document.visibilityState!=="hidden"&&U()};return document.addEventListener("visibilitychange",x),x(),()=>{$=!1,L&&clearInterval(L),document.removeEventListener("visibilitychange",x)}},[G]),j.useEffect(()=>{k&&Z.current&&(Z.current.scrollTop=Z.current.scrollHeight)},[f,k]);const Q=f.filter(L=>(F==="all"||L.level===F)&&(!K||String(L.component||"").toLowerCase().includes(K.toLowerCase()))),B=Q.map(L=>`${L.ts} ${L.level.toUpperCase()} ${L.component} ${L.code} ${L.message}${Object.keys(L.meta||{}).length?` ${JSON.stringify(L.meta)}`:""}`).join(`
|
|
92
92
|
`),M=async L=>{y("");try{const $=await L();p($),await G()}catch($){y(String($.message||$))}},N=async()=>{try{await navigator.clipboard.writeText(B)}catch{y(m("copy-manual"))}},T=()=>{try{const L=new Blob([`${JSON.stringify(Q,null,2)}
|
|
93
|
-
`],{type:"application/json"}),$=URL.createObjectURL(L),U=document.createElement("a");U.href=$,U.download="nexuscrew-diagnostics.json",U.click(),URL.revokeObjectURL($)}catch{y(m("diagnostics-export-failed"))}};return c.jsxs("div",{className:"nc-set-tab nc-diagnostics",children:[c.jsxs("label",{className:"nc-field",children:[m("diagnostics-target"),c.jsx("select",{value:u.key,onChange:L=>a(L.target.value),children:s.map(L=>c.jsxs("option",{value:L.key,children:[L.label,L.status!=="up"?` · ${L.status}`:""]},L.key))})]}),c.jsxs("div",{className:"nc-set-form nc-diag-controls",children:[c.jsx("div",{className:"nc-sheet-label",children:m("diagnostics-verbose")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("select",{"aria-label":m("diagnostics-duration"),value:R,onChange:L=>E(Number(L.target.value)),disabled:n,children:[300,900,1800,3600].map(L=>c.jsxs("option",{value:L,children:[L/60," min"]},L))}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:n||(h==null?void 0:h.verbose)===!0,onClick:()=>M(()=>xg(e,!0,R,u.route)),children:m("enable")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||(h==null?void 0:h.verbose)!==!0,onClick:()=>M(()=>xg(e,!1,R,u.route)),children:m("stop")})]}),c.jsx("small",{className:"nc-set-hint",children:h!=null&&h.verbose?m("diagnostics-expires").replace("{time}",new Date(h.expiresAt).toLocaleTimeString()):m("diagnostics-off")})]}),c.jsxs("div",{className:"nc-diag-filters",children:[c.jsxs("select",{"aria-label":m("diagnostics-level"),value:F,onChange:L=>O(L.target.value),children:[c.jsx("option",{value:"all",children:m("diagnostics-all-levels")}),["debug","info","warn","error"].map(L=>c.jsx("option",{value:L,children:L},L))]}),c.jsx("input",{"aria-label":m("diagnostics-component"),placeholder:m("diagnostics-component"),value:K,onChange:L=>Y(L.target.value)})]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:b,onChange:L=>w(L.target.checked)})," ",m("diagnostics-pause")]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:k,onChange:L=>C(L.target.checked)})," ",m("diagnostics-autoscroll")]})]}),c.jsx("pre",{ref:Z,className:"nc-diag-log","aria-label":m("diagnostics-log"),children:B||m("diagnostics-empty")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:N,children:m("copy")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:T,children:m("diagnostics-export")}),c.jsx("button",{type:"button",className:"nc-btn danger",disabled:n,onClick:()=>{window.confirm(m("diagnostics-clear-confirm"))&&M(async()=>{const L=await q0(e,u.route);return z.current=0,v([]),L})},children:m("diagnostics-clear")})]}),S&&c.jsx("div",{className:"nc-err",children:S})]})}function jj({token:e,settings:t,readonly:n,refresh:s,roster:o,section:a,setSection:u}){const[h,p]=j.useState(null),[f,v]=j.useState(null),[S,y]=j.useState(!1),[b,w]=j.useState(!1),[k,C]=j.useState(null),[R,E]=j.useState(!0),[F,O]=j.useState(!1);j.useEffect(()=>{C(t&&t.update||null),E(!t||t.autoUpdate!==!1),O(!!(t&&t.alternateScreen))},[t]);const K=async()=>{p(null),v(null),w(!0);try{const N=await C0(e);v(N.note||"ok"),y(!1)}catch(N){p(String(N.message||N))}w(!1)},Y=async()=>{p(null),v(null),w(!0);try{const N=await $0(e);v(`${N.note||"ok"}${N.target?` (${N.target})`:""}`)}catch(N){p(String(N.message||N))}w(!1)},z=async N=>{p(null),v(null),w(!0);try{await Ud(e,{autoUpdate:N}),E(N),s&&await s()}catch(T){p(String(T.message||T))}w(!1)},Z=async N=>{p(null),v(null),w(!0);try{await Ud(e,{alternateScreen:N}),O(N),s&&await s()}catch(T){p(String(T.message||T))}w(!1)},G=async()=>{p(null),v(null),w(!0);try{C(await H0(e))}catch(N){p(String(N.message||N))}w(!1)},Q=async()=>{p(null),v(null),w(!0);try{const N=await U0(e);C(N),v(m("npm-update-restarting"))}catch(N){p(String(N.message||N))}w(!1)},B=()=>{window.confirm(m("composer-clear-confirm"))&&(p(null),WE()?(window.dispatchEvent(new Event(Dh)),v(m("composer-clear-done"))):p(m("composer-clear-failed")))},M=t&&t.service;return c.jsxs("div",{className:"nc-set-tab",children:[c.jsx("div",{className:"nc-system-tabs",role:"tablist","aria-label":m("tab-system"),children:["general","diagnostics"].map(N=>c.jsx("button",{type:"button",role:"tab","aria-selected":a===N,className:`nc-set-tabbtn${a===N?" on":""}`,onClick:()=>u(N),children:m(`system-${N}`)},N))}),a==="general"&&c.jsxs(c.Fragment,{children:[c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-this-node")}),t&&c.jsxs("div",{className:"nc-set-info",children:["v",t.version," · ",t.platform," · :",t.port,c.jsx("br",{}),M&&M.installed?m("service-installed"):m("service-missing"),M&&M.installed?` · ${M.active?m("service-active"):m("service-inactive")}`:"",M?` · boot ${M.boot?"on":"off"}`:""]}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||b,title:n?m("settings-readonly"):"",onClick:Y,children:m("service-regenerate")})}),c.jsx(xj,{token:e,readonly:n}),c.jsx(kj,{})]}),c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-updates")}),c.jsxs("div",{className:"nc-set-form nc-update-settings",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:R,disabled:n||b||k&&!k.supported,onChange:N=>z(N.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:m("npm-auto-update")}),c.jsx("small",{children:m("npm-auto-update-help")})]})]}),k&&c.jsxs("div",{className:"nc-set-info",children:[m("npm-update-current")," v",k.current,k.latest?` · ${m("npm-update-latest")} v${k.latest}`:""," · ",m(`npm-update-${k.supported?k.phase:"unsupported"}`)]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||b||!k||!k.supported,onClick:G,children:m("npm-update-check")}),k&&k.available&&c.jsx("button",{type:"button",className:"nc-btn primary",disabled:n||b||k.phase==="installing",onClick:Q,children:m("npm-update-install")})]}),k&&k.lastError&&c.jsx("div",{className:"nc-err",children:k.lastError})]})]}),c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-security")}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||b,title:n?m("settings-readonly"):"",onClick:()=>{p(null),v(null),y(!0)},children:m("token-rotate")})})]}),c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-local-data")}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:m("composer-clear-data")}),c.jsx("small",{className:"nc-set-hint",children:m("composer-clear-data-help")}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:B,children:m("composer-clear-data")})})]})]})]}),a==="diagnostics"&&c.jsxs("section",{className:"nc-system-group nc-system-diagnostics",children:[c.jsx("h3",{children:m("system-diagnostics")}),c.jsxs("div",{className:"nc-set-form nc-alternate-screen-settings",children:[c.jsx("div",{className:"nc-sheet-label",children:m("system-terminal")}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox","aria-label":m("alternate-screen"),checked:F,disabled:n||b,onChange:N=>Z(N.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:m("alternate-screen")}),c.jsx("small",{children:m("alternate-screen-help")})]})]})]}),c.jsx(Nj,{token:e,roster:o,readonly:n})]}),S&&c.jsxs("div",{className:"nc-set-confirm",children:[c.jsx("div",{children:m("token-rotate-explain")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:b,onClick:()=>y(!1),children:m("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:b,onClick:K,children:m("confirm")})]})]}),f&&c.jsx("div",{className:"nc-set-note",children:f}),h&&c.jsx("div",{className:"nc-err",children:h})]})}function Rj({token:e,onClose:t,initialTab:n="nodes",initialLocation:s="",startNewCell:o=!1}){kn();const a=n==="diagnostics"?"diagnostics":"general",[u,h]=j.useState(n==="diagnostics"?"system":n),p=j.useRef(null),f=j.useRef(null),[v,S]=j.useState({start:!1,end:!1}),[y,b]=j.useState(a),[w,k]=j.useState(null),[C,R]=j.useState([]),[E,F]=j.useState([]),[O,K]=j.useState(!1),[Y,z]=j.useState(null),[Z,G]=j.useState(0),Q=nf(e,!0,Z),B=j.useCallback(()=>{const N=p.current;if(!N)return;const T=N.scrollWidth>N.clientWidth+1,L={start:T&&N.scrollLeft>1,end:T&&N.scrollLeft+N.clientWidth<N.scrollWidth-1};S($=>$.start===L.start&&$.end===L.end?$:L)},[]);j.useEffect(()=>{const N=p.current;if(!N)return;const T=typeof ResizeObserver=="function"?new ResizeObserver(B):null;return T==null||T.observe(N),window.addEventListener("resize",B),B(),()=>{T==null||T.disconnect(),window.removeEventListener("resize",B)}},[B]),j.useEffect(()=>{var T,L;(L=(T=f.current)==null?void 0:T.scrollIntoView)==null||L.call(T,{behavior:"smooth",block:"nearest",inline:"nearest"});const N=window.requestAnimationFrame?window.requestAnimationFrame(B):setTimeout(B,0);return()=>{window.cancelAnimationFrame?window.cancelAnimationFrame(N):clearTimeout(N)}},[u,B]);const M=j.useCallback(async()=>{var N;try{const T=await ey(e);k(T),z(null)}catch(T){z(String(T.message||T))}try{const L=(await x0(e)).peers||[];let $=[],U=[];try{const[x,I]=await Promise.all([Ht("/api/config",e).then(X=>X.json()).catch(()=>null),ny(e).catch(()=>null)]),D=x&&typeof x.instanceId=="string"?x.instanceId:"",A=[{instanceId:D||null,route:[],label:null},...dy(I,D)],W=await Promise.all(A.map(async X=>{try{return{ok:!0,peers:((await ty(e,X.route)).nodes||[]).map(fe=>uy(fe,X)).filter(Boolean)}}catch(ie){return{ok:!1,owner:X,error:ie}}}));for(const X of W){if(X.ok){$.push(...X.peers);continue}X.owner.route.length!==0&&U.push({instanceId:X.owner.instanceId,label:X.owner.label,route:X.owner.route,failure:/timeout/i.test(String(((N=X.error)==null?void 0:N.message)||X.error))?"timeout":"unreachable"})}}catch{}F(U),R([...L,...$])}catch(T){z(String(T.message||T))}},[e]);return j.useEffect(()=>{M();const N=setInterval(M,5e3);return()=>clearInterval(N)},[M]),j.useEffect(()=>{let N=!1;return Ht("/api/config",e).then(T=>T.json()).then(T=>{N||K(!!T.readonlyDefault)}).catch(()=>{}),()=>{N=!0}},[e]),c.jsx("div",{className:"nc-set-overlay",onClick:t,children:c.jsxs("div",{className:"nc-set-panel",onClick:N=>N.stopPropagation(),children:[c.jsxs("div",{className:"nc-set-head",children:[c.jsx(Ze,{name:"gear",size:18}),c.jsx("b",{children:m("settings")}),c.jsx("button",{type:"button",className:"nc-set-close",onClick:t,title:m("close"),children:c.jsx(Ze,{name:"x",size:18})})]}),O&&c.jsx("div",{className:"nc-set-readonly",children:m("settings-readonly")}),Y&&c.jsx("div",{className:"nc-err",children:Y}),c.jsx("div",{className:`nc-set-tabs-wrap${v.start?" has-start-overflow":""}${v.end?" has-end-overflow":""}`,children:c.jsx("div",{className:"nc-set-tabs",ref:p,onScroll:B,children:["nodes","fleet","audio","input","system"].map(N=>c.jsx("button",{ref:u===N?f:null,type:"button",className:`nc-set-tabbtn${u===N?" on":""}`,onClick:()=>h(N),children:m(`tab-${N}`)},N))})}),c.jsxs("div",{className:"nc-set-body",children:[u==="nodes"&&c.jsx(Sj,{token:e,nodes:C,roster:Q,settings:w,readonly:O,vlUnavailable:E,refresh:M,refreshAliases:()=>G(N=>N+1)}),u==="fleet"&&c.jsx(ij,{token:e,readonly:O,startNewCell:o,initialLocation:s,targets:Q.map(N=>({route:N.route,label:N.label||N.name,status:N.status}))}),u==="audio"&&c.jsx(Cj,{token:e,readonly:O,nodes:C,settings:w}),u==="input"&&c.jsx(Ej,{}),u==="system"&&c.jsx(jj,{token:e,settings:w,readonly:O,refresh:M,roster:Q,section:y,setSection:b})]})]})})}function Pj({token:e,initialPair:t,deviceDefault:n="",localNodeId:s="",localNameDefault:o="",onPairDone:a,onDone:u}){kn();const[h,p]=j.useState(t?"pair":"welcome"),[f,v]=j.useState(!1),[S,y]=j.useState(null),b=async()=>{v(!0),y(null);try{await Ud(e,{wizardDone:!0}),a&&a(),u()}catch(w){y(String(w.message||w)),v(!1)}};return c.jsx("div",{className:"nc-wiz-overlay",children:c.jsxs("div",{className:"nc-wiz",children:[c.jsxs("div",{className:"nc-wiz-head",children:[c.jsx("b",{children:m("wizard-title")}),c.jsx("small",{children:m("hydra-simple")})]}),h==="welcome"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:m("local-ready")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:b,children:m("local-only")}),c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:()=>p("pair"),children:m("add-node")})]})]}),h==="pair"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx(Yw,{token:e,initial:t||"",autoStart:!!t,deviceDefault:n,localNodeId:s,localNameDefault:o,onBusyChange:v,onSuccess:async()=>{a&&a(),p("done")}}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:()=>{a&&a(),p("welcome")},children:m("back")})})]}),h==="done"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:m("node-connected")}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:b,children:m("finish")})})]}),S&&c.jsx("div",{className:"nc-err",children:S})]})})}function Lj(e,t){if(typeof EventSource>"u"||!e)return()=>{};const n=new EventSource(`/api/events?token=${encodeURIComponent(e)}`);return n.onmessage=s=>{try{const o=JSON.parse(s.data);o&&typeof o=="object"&&typeof o.type=="string"&&t(o)}catch{}},()=>{try{n.close()}catch{}}}const Aj=6e3,Tj=12e3;function Dj({n:e,onClose:t}){return c.jsxs("div",{className:`nc-ntf-toast${e.urgency==="high"?" high":""}`,role:"status","aria-live":"polite",children:[c.jsxs("div",{className:"nc-ntf-toast-txt",children:[c.jsx("b",{children:e.title}),e.body&&c.jsx("small",{children:e.body}),e.session&&c.jsx("span",{className:"nc-ntf-from",children:e.session})]}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:t,title:m("close"),children:c.jsx(Ze,{name:"x",size:14})})]})}function Mj({ask:e,token:t,onAnswered:n,onDismiss:s}){const[o,a]=j.useState(""),[u,h]=j.useState(!1),[p,f]=j.useState(null),[v,S]=j.useState(!1),y=async w=>{const k=String(w||"").trim();if(!(!k||u)){f(null),h(!0);try{await Y0(t,e.id,k),n(e.id)}catch(C){f(String(C.message||C))}h(!1)}},b=async()=>{if(!v){S(!0);try{await G0(t,e.id),s(e.id)}catch{S(!1)}}};return c.jsxs("div",{className:"nc-ask-card",children:[c.jsxs("div",{className:"nc-ask-head",children:[c.jsx("span",{className:"nc-ntf-from",children:e.session}),c.jsxs("code",{className:"nc-ask-id",children:["#",e.id]}),c.jsx("button",{type:"button",className:"nc-ask-dismiss",title:m("ask-dismiss"),"aria-label":m("ask-dismiss"),disabled:v,onClick:b,children:c.jsx(Ze,{name:"x",size:14})})]}),c.jsx("div",{className:"nc-ask-q",children:e.question}),Array.isArray(e.options)&&e.options.length>0&&c.jsx("div",{className:"nc-ask-opts",children:e.options.map(w=>c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:u,onClick:()=>y(w),children:w},w))}),c.jsxs("div",{className:"nc-ask-reply",children:[c.jsx("textarea",{rows:2,placeholder:m("ask-reply-ph"),value:o,disabled:u,onChange:w=>a(w.target.value)}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:u||!o.trim(),onClick:()=>y(o),children:m("send")})]}),p&&c.jsx("div",{className:"nc-err",children:p})]})}function Ij({token:e}){const[t]=kn(),[n]=ub(),[s,o]=j.useState([]),[a,u]=j.useState([]),[h,p]=j.useState(()=>{try{return/(?:^|[#&])ask=/.test(location.hash)}catch{return!1}}),f=j.useRef(0),v=j.useRef(null);v.current||(v.current=yj());const S=j.useRef({enabled:n,lang:t});S.current={enabled:n,lang:t};const y=j.useCallback(C=>{o(R=>R.filter(E=>E.key!==C))},[]),b=j.useCallback(C=>{const R=`t${f.current+=1}`;o(F=>[...F.slice(-3),{...C,key:R}]),setTimeout(()=>y(R),C.urgency==="high"?Tj:Aj);const E=S.current;if(E.enabled)try{v.current.enqueue(C,pj(C,E.lang))}catch{}},[y]);j.useEffect(()=>{n||v.current.stop()},[n]),j.useEffect(()=>{const C=()=>{(document.visibilityState!=="visible"||!document.hasFocus())&&v.current.stop()},R=()=>v.current.stop();return document.addEventListener("visibilitychange",C),window.addEventListener("blur",C),window.addEventListener(Fh,R),()=>{document.removeEventListener("visibilitychange",C),window.removeEventListener("blur",C),window.removeEventListener(Fh,R),typeof v.current.dispose=="function"?v.current.dispose():v.current.stop()}},[]),j.useEffect(()=>{if(!e)return;let C=!1;V0(e).then(E=>{C||u(E.asks||[])}).catch(()=>{});const R=Lj(e,E=>{E.type==="notify"?b(E):E.type==="ask"&&E.ask&&E.ask.id?u(F=>F.some(O=>O.id===E.ask.id)?F:[...F,E.ask]):E.type==="ask-answered"&&E.id?u(F=>F.filter(O=>O.id!==E.id)):E.type==="ask-dismissed"&&E.id&&u(F=>F.filter(O=>O.id!==E.id))});return()=>{C=!0,R()}},[e,b]);const w=C=>u(R=>R.filter(E=>E.id!==C)),k=C=>u(R=>R.filter(E=>E.id!==C));return c.jsxs(c.Fragment,{children:[s.length>0&&c.jsx("div",{className:"nc-ntf-toasts",children:s.map(C=>c.jsx(Dj,{n:C,onClose:()=>y(C.key)},C.key))}),a.length>0&&!h&&c.jsxs("button",{type:"button",className:"nc-ask-badge",onClick:()=>p(!0),title:m("asks-title"),children:["? ",c.jsx("span",{className:"nc-ask-count",children:a.length})]}),a.length>0&&h&&c.jsxs("div",{className:"nc-ask-panel",children:[c.jsxs("div",{className:"nc-ask-panel-head",children:[c.jsx("b",{children:m("asks-title")}),c.jsx("span",{className:"nc-ask-count",children:a.length}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:()=>p(!1),title:m("close"),children:c.jsx(Ze,{name:"x",size:16})})]}),c.jsx("div",{className:"nc-ask-panel-body",children:a.map(C=>c.jsx(Mj,{ask:C,token:e,onAnswered:w,onDismiss:k},C.id))})]})]})}const Oj=4e3;async function Bj(e){const t=await Ht("/api/sessions",e);if((t==null?void 0:t.ok)===!1)throw new Error(`HTTP ${t.status}`);const n=await t.json();if(!Array.isArray(n==null?void 0:n.sessions))throw new Error("invalid sessions payload");return n.sessions}async function Fd(e,t=[]){var h;const[n,s]=await Promise.allSettled([t.length?Jh(e,t):Bj(e),ws(e,t)]),o=n.status==="fulfilled"&&Array.isArray((h=n.value)==null?void 0:h.sessions)?n.value.sessions:Array.isArray(n.value)?n.value:null,a=s.status==="fulfilled"?s.value:null,u=(a==null?void 0:a.available)===!0&&Array.isArray(a.cells)?a.cells:null;return{sessions:o,cells:u,fresh:Array.isArray(o)&&Array.isArray(u)}}function db(e,t,n){return n===!0&&(e==null?void 0:e.degraded)!==!0&&(e==null?void 0:e.active)===!0&&e.tmux!==!1&&!!e.tmuxSession&&(t||[]).some(s=>(s==null?void 0:s.name)===e.tmuxSession)}function zj(e){const t=new Map((e.sessions||[]).map(o=>[o.name,o])),n=[],s=(o,a,u,h=[],p="")=>{const f=new Map((a||[]).map(v=>[v.name,v]));for(const v of o||[]){const S=f.get(v.tmuxSession)||{},y=of(v,S),b=db(v,a,u);n.push({key:Go(h,v.tmuxSession||v.cell),session:v.tmuxSession,route:h,cellName:v.cell,label:v.cell,node:h.length?h.join("/"):"",nodeLabel:p,live:b,selectable:b,verified:u===!0,working:y.working,degraded:!!v.degraded,active:v.active===!0,activity:S.activity||v.activity||0,subtitle:y.subtitle})}};s(e.cells,[...t.values()],e.localFresh===!0);for(const o of e.nodeGroups||[]){const a=Array.isArray(o.route)?o.route:[];o.kind==="vl"||!a.length||s(o.cells,o.sessions,o.switcherFresh===!0,a,o.label||o.name||"")}return n}function Fj(e){const t=new Map,n=Array.isArray(e.sessions)?e.sessions:[],s=Array.isArray(e.cells)?e.cells:[],o=new Map(n.map(u=>[u.name,u])),a=new Set(s.map(u=>u.tmuxSession).filter(Boolean));t.set("local",af(s,n.filter(u=>!a.has(u.name)),o));for(const u of e.nodeGroups||[]){const h=Array.isArray(u.route)?u.route:[];if(u.kind==="vl"||!h.length)continue;const p=Array.isArray(u.cells)?u.cells:[],f=Array.isArray(u.sessions)?u.sessions:[],v=new Set(p.map(S=>S.tmuxSession).filter(Boolean));t.set(h.join("/"),Fl({...u,route:h,cells:p,sessions:f,unmanaged:f.filter(S=>!v.has(S.name))}).rawItems)}return t}function $j(e,t,n,s){return[...new Set(e.map(a=>a.node||"local"))].flatMap(a=>{const u=e.filter(v=>(v.node||"local")===a),h=new Map((t.get(a)||[]).map(v=>[v.key,v])),p=new Map(u.map(v=>[v.key,v])),f=u.map(v=>{const S=h.get(v.key);return{...v,label:(S==null?void 0:S.label)||v.label,live:(S==null?void 0:S.live)??v.live,fresh:(S==null?void 0:S.fresh)===!0,activity:(S==null?void 0:S.activity)??v.activity}});return gs(f,n,"all",Fi(s,a)).map(v=>p.get(v.key))})}function Hj({token:e,current:t,onPick:n,onClose:s}){const[o,a]=j.useState(Ig),[u,h]=j.useState(!1),[p,f]=j.useState(!1),[v,S]=j.useState(""),[y,b]=j.useState(""),[w,k]=j.useState(""),C=j.useRef(null),R=j.useRef(null),E=j.useMemo(()=>zj(o),[o]),F=j.useMemo(()=>Fj(o),[o]),{pins:O,orders:K,canMoveRoster:Y,moveRoster:z,stepRoster:Z}=rf(),G=j.useMemo(()=>$j(E,F,O,K),[E,F,O,K]),Q=j.useMemo(()=>u?G:G.filter(L=>L.selectable||L.degraded&&L.active),[G,u]),B=j.useMemo(()=>E.find(L=>L.key===y&&L.selectable),[E,y]);j.useEffect(()=>{var U;const L=document.activeElement;(U=R.current)==null||U.focus();const $=x=>{x.key==="Escape"&&s()};return document.addEventListener("keydown",$),()=>{var x;document.removeEventListener("keydown",$),(x=L==null?void 0:L.focus)==null||x.call(L)}},[s]),j.useEffect(()=>{let L=!0,$=!1;const U=async()=>{if($)return;$=!0;const I=Ig(),D=Array.isArray(I.nodeGroups)?I.nodeGroups:[],A=Fd(e),W=pe=>pe.kind!=="vl"&&Array.isArray(pe.route)&&pe.route.length>0,X=await Promise.all(D.filter(W).map(async pe=>({group:pe,result:await Fd(e,pe.route)}))),ie=await A;if(!L)return;const le=new Map(X.map(({group:pe,result:_e})=>[JSON.stringify(pe.route||[]),_e])),fe=D.map(pe=>{if(!W(pe))return{...pe,switcherFresh:!1};const _e=le.get(JSON.stringify(pe.route||[]));return _e?{...pe,sessions:_e.sessions||pe.sessions||[],cells:_e.cells||pe.cells||[],switcherFresh:_e.fresh}:{...pe,switcherFresh:!1}}),we=by({...I,sessions:ie.sessions||I.sessions||[],cells:ie.cells||I.cells||[],localFresh:ie.fresh,nodeGroups:fe,refreshedAt:Date.now()});a(we),f(!0),$=!1};U();const x=setInterval(U,Oj);return()=>{L=!1,clearInterval(x)}},[e]);const M=L=>L.degraded?m("cell-degraded"):L.verified?L.selectable?L.working?m("cell-working"):m("cell-idle"):m("cell-off"):m("cell-switcher-not-confirmed"),N=L=>{if(S(""),!L.selectable){S(m("cell-switcher-not-active"));return}b(L.key)},T=async()=>{const L=B;if(!L){S(m("cell-switcher-select"));return}S(""),k(L.key);try{const $=await Fd(e,L.route),U=($.cells||[]).find(x=>(x==null?void 0:x.cell)===L.cellName&&(x==null?void 0:x.tmuxSession)===L.session);if(!db(U,$.sessions,$.fresh)){S(m("cell-switcher-not-active"));return}n({session:L.session,...L.node?{node:L.node}:{},cellName:L.cellName}),s()}catch{S(m("cell-switcher-not-active"))}finally{k("")}};return c.jsx("div",{className:"nc-cell-switcher-backdrop",onClick:s,children:c.jsxs("aside",{ref:C,className:"nc-cell-switcher",role:"dialog","aria-label":m("fleet-cells"),tabIndex:-1,onClick:L=>L.stopPropagation(),children:[c.jsxs("div",{className:"nc-cell-switcher-list",children:[!p&&c.jsx("div",{className:"nc-empty",role:"status",children:m("cell-switcher-refreshing")}),p&&Q.length===0&&c.jsx("div",{className:"nc-empty",role:"status",children:m("cell-switcher-empty-active")}),Q.map(L=>{const $=(t==null?void 0:t.session)===L.session&&((t==null?void 0:t.node)||"")===L.node,U=y===L.key,x=M(L),I=L.node||"local",D=F.get(I)||E.filter(A=>(A.node||"local")===I);return c.jsxs("div",{className:`nc-cell-switcher-row${$?" current":""}${U?" selected":""}${L.selectable?"":" off"}`,"data-roster-key":L.key,"data-position":I,children:[c.jsx(bi,{position:I,itemKey:L.key,label:L.cellName,canMove:Y,onMove:(A,W)=>z(I,A,W,D),onStep:A=>Z(I,L.key,A,D)}),c.jsxs("button",{type:"button",className:"nc-cell-switcher-row-select","aria-current":$?"true":void 0,"aria-pressed":U,"aria-disabled":!L.selectable,disabled:w===L.key,onClick:()=>N(L),children:[c.jsx("span",{className:`nc-cell-switcher-dot${L.degraded?" warn":L.live?` on${L.working?" working":""}`:""}`}),c.jsxs("span",{className:"nc-cell-switcher-copy",children:[c.jsx("b",{children:L.cellName}),c.jsx("small",{className:"nc-cell-switcher-state",children:x}),c.jsx("small",{children:[L.nodeLabel,L.subtitle].filter(Boolean).join(" · ")})]})]})]},L.key)})]}),v&&c.jsx("div",{className:"nc-cell-switcher-notice",role:"status",children:v}),c.jsx("div",{className:"nc-cell-switcher-actions",children:c.jsx("button",{type:"button",className:"nc-cell-switcher-open",disabled:!B||!!w,onClick:T,children:B?`${m("cell-switcher-open")}: ${B.cellName}`:m("cell-switcher-select")})}),c.jsxs("div",{className:"nc-cell-switcher-controls",children:[c.jsx("b",{children:m("fleet-cells")}),c.jsx("button",{type:"button",className:"nc-cell-switcher-filter","aria-pressed":u,onClick:()=>{S(""),h(L=>!L)},children:m(u?"cell-switcher-show-active":"cell-switcher-show-all")}),c.jsx("button",{ref:R,type:"button",className:"nc-cell-switcher-close","aria-label":m("cell-switcher-close"),title:m("cell-switcher-close"),onClick:s,children:"×"})]})]})})}const Uj=2e3;function Wj(e){if(e.kind==="gap")return`${m("vl-events-gap")} ${e.count||"?"}`;if(e.kind==="truncate")return m("vl-events-truncated");if(e.kind==="turn_end")return m("vl-events-turn-end");if(e.kind==="done")return m("vl-events-done");if(e.kind==="writer_epoch")return m("vl-events-writer-epoch");if(e.kind==="tool_start")return`${m("vl-events-tool")} ${e.name||""}`.trim();if(e.kind==="tool_end"){const t=e.isError?m("vl-events-tool-failed"):m("vl-events-tool-ok");return`${m("vl-events-tool")} ${e.name||""} — ${t}`.trim()}return e.kind==="usage"?e.text||m("vl-events-usage"):e.text||""}function Kj({token:e,nodeId:t,route:n=[]}){const[s,o]=j.useState([]),[a,u]=j.useState(!1),h=j.useRef(0),p=j.useRef(!1);return j.useEffect(()=>{let f=!0;h.current=0,p.current=!1,o([]);const v=async()=>{if(!p.current){p.current=!0;try{const b=await k0(e,t,h.current,n);if(!f)return;u(!1);const k=(Array.isArray(b==null?void 0:b.events)?b.events:[]).filter(C=>Number.isSafeInteger(C==null?void 0:C.seq)&&C.seq>h.current);k.length&&(o(C=>C.concat(k).slice(-500)),h.current=k[k.length-1].seq),Number.isSafeInteger(b==null?void 0:b.cursor)&&b.cursor>h.current&&(h.current=b.cursor)}catch{f&&u(!0)}finally{p.current=!1}}},S=()=>{v().catch(()=>{})};S();const y=setInterval(S,Uj);return()=>{f=!1,clearInterval(y)}},[e,t,JSON.stringify(n)]),c.jsxs("div",{className:"nc-vl-events",children:[a&&c.jsx("div",{className:"nc-set-hint nc-vl-events-stale",children:m("vl-events-stale")}),s.length===0&&!a&&c.jsx("small",{className:"nc-set-hint","data-testid":"vl-events-empty",children:m("vl-events-empty")}),s.length>0&&c.jsx("ol",{className:"nc-vl-events-list",children:s.map(f=>c.jsx("li",{"data-testid":`vl-ev-${f.seq}`,className:`nc-vl-ev nc-vl-ev-${f.kind}${f.isError?" error":""}${f.kind==="gap"?" gap":""}`,children:Wj(f)},`${f.seq}-${f.kind}`))})]})}function F_({peer:e,token:t,onBack:n}){var s;return kn(),e?c.jsxs("section",{className:"nc-vl-session-view",children:[c.jsxs("header",{className:"nc-bar nc-bar-single",children:[c.jsxs("button",{onClick:n,title:m("back"),children:[c.jsx(Ze,{name:"chevronLeft",size:18}),c.jsx("span",{className:"nc-bar-label",children:m("back")})]}),c.jsxs("span",{className:"nc-bar-center",children:[c.jsx("b",{title:e.label,children:e.label}),((s=e.session)==null?void 0:s.profile)&&c.jsxs("small",{className:"nc-bar-sub",children:[e.session.profile," · ",m("vl-events-title")]})]}),c.jsx("span",{className:"nc-bar-right"})]}),c.jsx("div",{className:"nc-vl-session-body",children:c.jsx(Kj,{token:t,nodeId:e.nodeId,route:e.route||[]})})]}):null}const $d=e=>ki(Ys(e)).length===0,Hd=e=>Array.isArray(e)?e.join("/"):"";function qj(e){const t=new Set,n=[];for(const s of Array.isArray(e)?e:[])!s||!Hn.test(String(s.instanceId||""))||!Array.isArray(s.route)||!s.route.length||t.has(s.instanceId)||(t.add(s.instanceId),n.push({instanceId:s.instanceId,route:[...s.route],label:String(s.label||s.name||s.route.join(" › ")),status:s.status||"offline"}));return n.sort((s,o)=>s.label.localeCompare(o.label))}function zo(e){return{instanceId:e.ownerId,route:e.ownerRoute||[],label:e.ownerLabel,status:e.available===!1?"offline":"up"}}function Hs(e,t,n,s=!1,o=!0){const a=t.instanceId;return{...e,id:Lr(s?null:a,e.name),ownerId:a,ownerRoute:s?[]:[...t.route],ownerLabel:s?"Local":t.label,local:s,available:o,ownerTopology:Array.isArray(n)?n:[],layout:Ih(e.layout,a,n)}}function Vj(e,t,n,s,o=[]){const[a,u]=j.useState([]),[h,p]=j.useState(!1),[f,v]=j.useState("idle"),[S,y]=j.useState(""),[b,w]=j.useState(""),k=j.useRef([]),C=j.useRef([]),R=j.useRef(""),E=j.useRef(t),F=j.useRef(n),O=j.useRef(!1),K=j.useRef(!0),Y=j.useRef(""),z=j.useMemo(()=>qj(o),[o]),Z=z.map(D=>`${D.instanceId}:${Hd(D.route)}:${D.status}:${D.label}`).join("|");C.current=z,F.current=n,E.current=t,k.current=a;const G=j.useCallback(D=>Oh(D.layout,R.current,C.current),[]),Q=j.useCallback((D,A=!0,W=E.current)=>{const X=qv(D,xl()),ie=k.current.some(fe=>fe.id===W);k.current=X,u(X);const le=X.find(fe=>fe.id===W);if(A&&le){K.current=!0;const fe=G(le);s(fe),le.local&&pN(le.name,fe)}else ie&&!le&&(K.current=!0,s(Pr()),y("deck non più condiviso dal nodo owner"));uN(X.filter(fe=>fe.local).map(fe=>fe.name))},[s,G]),B=j.useCallback(async D=>{let A=await pl(e);const W=A.decks.find(X=>X.name==="main");if(A.decks.length===1&&W&&W.revision===0&&$d(W.layout)){const X=cN(),ie=Ys(Bh("main")||Pr());if(!$d(ie)){const le=Ih(ie,D,[]);A.decks[0]=await ml(e,"main",le,0,[])}for(const le of X.filter(fe=>fe!=="main"))try{let fe=await kg(e,le,[]);const we=Ys(Bh(le)||Pr());$d(we)||(fe=await ml(e,le,Ih(we,D,[]),fe.revision,[]))}catch{}A=await pl(e)}return A},[e]),M=j.useCallback(async({migrate:D=!1}={})=>{const A=await Zh(e,[]),W=Hn.test(String(A.instanceId||""))?A.instanceId:"";if(!W)throw new Error("instanceId locale non disponibile");R.current=W,w(W);const[X,ie]=await Promise.all([D?B(W):pl(e),bg(e,[]).catch(()=>({nodes:[]}))]),le={instanceId:W,route:[],label:"Local"},fe=X.decks.map(pe=>Hs(pe,le,ie.nodes,!0,!0)),we=k.current;return await Promise.all(C.current.map(async pe=>{if(pe.status!=="up"){fe.push(...we.filter(_e=>!_e.local&&_e.ownerId===pe.instanceId).map(_e=>({..._e,ownerRoute:[...pe.route],ownerLabel:pe.label,available:!1})));return}try{const[_e,He]=await Promise.all([pl(e,pe.route),bg(e,pe.route).catch(()=>({nodes:[]}))]);fe.push(..._e.decks.map(ht=>Hs(ht,pe,He.nodes,!1,!0)))}catch{fe.push(...we.filter(He=>!He.local&&He.ownerId===pe.instanceId).map(He=>({...He,ownerRoute:[...pe.route],ownerLabel:pe.label,available:!1})))}})),fe.sort((pe,_e)=>pe.local===_e.local?pe.ownerLabel.localeCompare(_e.ownerLabel)||(pe.name==="main"?-1:_e.name==="main"?1:pe.name.localeCompare(_e.name)):pe.local?-1:1),fe},[e,B,Z]);j.useEffect(()=>{if(!e)return;let D=!1;const A=Y.current!==e;return Y.current=e,M({migrate:A}).then(W=>{if(D)return;const X=k.current.find(fe=>fe.id===E.current),ie=W.find(fe=>fe.id===E.current),le=ie&&(!X||ie.revision>X.revision||X.available!==ie.available||Hd(X.ownerRoute)!==Hd(ie.ownerRoute));Q(W,A||!!le&&!O.current),p(!0),ie&&y("")}).catch(W=>{D||(p(!0),y(String(W.message||W)))}),()=>{D=!0}},[Q,M,Z,e]);const N=j.useCallback(async(D=E.current)=>{if(!h||!O.current)return!0;const A=k.current.find(W=>W.id===D);if(!A)return y(`deck inesistente: ${D}`),!1;if(A.available===!1)return y(`nodo owner offline: ${A.ownerLabel}`),!1;v("saving");try{const W=Wv(Ys(F.current),A.ownerId,A.ownerTopology),X=await ml(e,A.name,W,A.revision,A.ownerRoute),ie=Hs(X,zo(A),A.ownerTopology,A.local,!0);return Q(k.current.map(le=>le.id===D?ie:le),!1),O.current=!1,v("saved"),y(""),setTimeout(()=>v("idle"),1500),!0}catch(W){if(v("error"),y(String(W.message||W)),W.status===409&&W.data&&W.data.current){const X=k.current.find(le=>le.id===D),ie=Hs(W.data.current,zo(X),X.ownerTopology,X.local,!0);Q(k.current.map(le=>le.id===D?ie:le),!1)}return!1}},[h,e,Q]);return j.useEffect(()=>{if(!h)return;if(K.current){K.current=!1;return}O.current=!0,v("saving");const D=setTimeout(N,650);return()=>clearTimeout(D)},[n,h,N]),j.useEffect(()=>{if(!h)return;const D=setInterval(async()=>{try{const A=await M(),W=k.current.find(le=>le.id===E.current),X=A.find(le=>le.id===E.current),ie=X&&(!W||X.revision>W.revision||W.available!==X.available);Q(A,ie&&!O.current)}catch{}},5e3);return()=>clearInterval(D)},[h,M,Q]),{decks:a,records:a,localNodeId:b,ready:h,saveState:f,error:S,setError:y,saveNow:N,select:async D=>{if(O.current&&!await N(E.current))throw new Error("salvataggio del deck corrente fallito: cambio annullato");const A=k.current.find(W=>W.id===D);if(!A)throw new Error(`deck inesistente: ${D}`);return O.current=!1,K.current=!0,G(A)},add:async(D,A=null)=>{const W=A===Si?k.current.find(le=>le.local):A?k.current.find(le=>le.ownerId===A):null;if(!W||W.available===!1)throw new Error("nodo owner non disponibile");const X=await kg(e,D,W.ownerRoute),ie=Hs(X,zo(W),W.ownerTopology,W.local,!0);return Q([...k.current,ie],!1),ie},rename:async(D,A)=>{const W=k.current.find(we=>we.id===D);if(!W)throw new Error("deck inesistente");if(D===E.current&&O.current&&!await N(D))throw new Error(`salvataggio di "${W.name}" fallito: rinomina annullata`);const X=k.current.find(we=>we.id===D);if(!X)throw new Error("deck inesistente");const ie=await X0(e,X.name,A,X.revision,X.ownerRoute),le=Hs(ie,zo(X),X.ownerTopology,X.local,!0),fe=X.local?Si:X.ownerId;return pd(hN(xl(),fe,D,le.id)),Q(k.current.map(we=>we.id===D?le:we),!1),le},remove:async D=>{const A=k.current.find(X=>X.id===D);if(!A)throw new Error("deck inesistente");D===E.current&&(O.current=!1),await Q0(e,A.name,A.revision,A.ownerRoute);const W=A.local?Si:A.ownerId;pd(fN(xl(),W,D)),Q(k.current.filter(X=>X.id!==D),!1)},reorder:(D,A)=>{const W=k.current.find(pe=>pe.id===D),X=k.current.find(pe=>pe.id===A);if(!W||!X)return!1;const ie=W.local?Si:W.ownerId,le=X.local?Si:X.ownerId;if(ie!==le)return!1;const fe=k.current.filter(pe=>(pe.local?Si:pe.ownerId)===ie).map(pe=>pe.id),we=pd(dN(xl(),ie,D,A,fe));return Q(qv(k.current,we),!1),!0},addTileTo:async(D,A)=>{const W=k.current.find(pe=>pe.id===D);if(!W)throw new Error("deck inesistente");if(W.available===!1)throw new Error(`nodo owner offline: ${W.ownerLabel}`);const X=Pw(A,R.current,C.current);if(!X)throw new Error("riferimento sessione non valido");const ie=G(W),le=Wv(_y(ie,X),W.ownerId,W.ownerTopology),fe=await ml(e,W.name,le,W.revision,W.ownerRoute),we=Hs(fe,zo(W),W.ownerTopology,W.local,!0);return Q(k.current.map(pe=>pe.id===D?we:pe),!1),we},localMainId:Lr(null,"main"),parseDeckId:sc}}const Yj="/sw.js",Hh="nc-sw-update";let Uh=null,Wh=!1,Kh=null,hb=Object.freeze({needed:!1,kind:null,version:""}),fb=!1;function pb(){typeof window<"u"&&window.dispatchEvent(new Event(Hh))}function mb(){const e=Kh||(Wh?{kind:"reload",version:""}:null);hb=Object.freeze({needed:!!e,kind:(e==null?void 0:e.kind)||null,version:(e==null?void 0:e.version)||""})}function $_(){return hb}function Gj(e){return typeof window>"u"?()=>{}:(window.addEventListener(Hh,e),()=>window.removeEventListener(Hh,e))}function Ff(e){Wh!==e&&(Wh=e,mb(),pb())}function Xj(e,t,n){let s=null;e&&t&&e!==t?s={kind:"install",version:e}:t&&n&&t!==n&&(s={kind:"reload",version:t});const o=JSON.stringify(s)===JSON.stringify(Kh);Kh=s,mb(),o||pb()}function Qj(e){e.addEventListener("statechange",()=>{e.state==="installed"&&navigator.serviceWorker.controller&&Ff(!0)})}function Jj(){typeof navigator>"u"||!("serviceWorker"in navigator)||(navigator.serviceWorker.addEventListener("controllerchange",()=>{fb&&location.reload()}),navigator.serviceWorker.register(Yj).then(e=>{Uh=e,e.waiting&&Ff(!0),e.addEventListener("updatefound",()=>{const t=e.installing;t&&Qj(t)}),setInterval(()=>{e.update().catch(()=>{})},3600*1e3)}).catch(()=>{}))}function Zj(){fb=!0,Ff(!1);const e=Uh&&Uh.waiting;e?(e.postMessage({type:"SKIP_WAITING"}),setTimeout(()=>location.reload(),4e3)):location.reload()}function eR({hash:e="",origin:t="",pathname:n="",search:s=""}={}){const o=String(e||"").replace(/^#/,""),a=`${n||""}${s||""}`;if(!o)return{token:"",pair:"",nextUrl:a};const u=new URLSearchParams(o),h=u.get("token")||"",p=u.get("pair")||"",f=p?`${t||""}${n||""}#pair=${p}`:"";return{token:h,pair:f,nextUrl:a}}const gb=9,vb=24,_b="nc_side_w",H_="nc_side_min",tR=240,U_="(min-width:1024px) and (pointer:fine)";function nR(){const e=Number(localStorage.getItem(_b));return e>=180&&e<=480?e:tR}function iR(){const e=Number(localStorage.getItem("nc_fontsize"));return e>=gb&&e<=vb?e:13}function sR(){const e={token:"",pair:""};try{const{token:t,pair:n,nextUrl:s}=eR({hash:location.hash,origin:location.origin,pathname:location.pathname,search:location.search});if(t){e.token=t;try{localStorage.setItem("nc_token",t)}catch{}}if(n){e.pair=n;try{sessionStorage.setItem("nc_pair",n)}catch{}}if(location.hash)try{history.replaceState(null,"",s)}catch{}}catch{}if(e.token||(e.token=sessionStorage.getItem("nc_token")||localStorage.getItem("nc_token")||""),!e.pair){const t=sessionStorage.getItem("nc_pair");t&&(e.pair=t)}return e}function rR(e){try{return Ys(Bh(e))}catch{return Pr()}}function W_(e){if(!e)return"";const t=Math.floor(Date.now()/1e3)-e;return t<0||t<60?"ora":t<3600?`${Math.floor(t/60)}m`:t<86400?`${Math.floor(t/3600)}h`:`${Math.floor(t/86400)}g`}function oR(){const[e,t]=j.useState(()=>window.matchMedia(U_).matches);return j.useEffect(()=>{const n=window.matchMedia(U_),s=o=>t(o.matches);return n.addEventListener("change",s),()=>n.removeEventListener("change",s)},[]),e}function K_({session:e,node:t,ownerId:n,cellName:s,token:o,readonly:a=!1,onBack:u,onCellSwitcher:h,cellSwitcherOpen:p=!1}){kn();const[f]=kf(),[v,S]=j.useState(!1),[y,b]=j.useState(()=>window.matchMedia("(pointer: coarse)").matches),[w,k]=j.useState(null),[C,R]=j.useState(iR),[E,F]=j.useState(s||e),[O,K]=j.useState(""),Y=$=>R(U=>{const x=Math.max(gb,Math.min(vb,U+$));return localStorage.setItem("nc_fontsize",String(x)),x}),z=j.useRef(()=>{}),Z=j.useRef(()=>!1),G=j.useRef(()=>{}),Q=j.useRef(!1),[B,M]=j.useState(!1),[N,T]=j.useState(!1),L=()=>{Q.current=!Q.current,M(Q.current)};return j.useEffect(()=>{F(s||e)},[s,e]),j.useEffect(()=>{let $=!0;const U=t?t.split("/"):[],x=t?`/api/route/${t.split("/").map(encodeURIComponent).join("/")}/_`:"/api";async function I(){let A=null,W=null;try{const le=await(await Ht(`${x}/sessions`,o)).json();Array.isArray(le.sessions)&&(A=le.sessions.find(fe=>fe.name===e))}catch{}try{const ie=await ws(o,U);ie.available&&Array.isArray(ie.cells)&&(W=ie.cells.find(le=>le.tmuxSession===e))}catch{}if(!$)return;F(Cf({session:e,cell:W||(s?{cell:s}:null)}));let X="";W?X=`${W.engine}${W.key?`·${W.key}`:""}`:A&&(X=A.attached?`attached · ${W_(A.activity)}`:A.activity?W_(A.activity):""),K(X)}I();const D=setInterval(I,4e3);return()=>{$=!1,clearInterval(D)}},[e,t,o]),c.jsxs("div",{className:"nc-app",children:[c.jsxs("header",{className:"nc-bar nc-bar-single",children:[c.jsxs("button",{onClick:u,title:m("sessions"),children:[c.jsx(Ze,{name:"chevronLeft",size:18}),c.jsx("span",{className:"nc-bar-label",children:m("sessions")})]}),c.jsxs("span",{className:"nc-bar-center",children:[c.jsx("b",{title:t?`${E} · ${t}`:E,children:E}),O&&c.jsx("small",{className:"nc-bar-sub",children:O})]}),c.jsxs("span",{className:"nc-bar-right",children:[c.jsx("button",{onClick:()=>Y(-1),title:m("zoom-out"),children:c.jsx(Ze,{name:"zoomOut",size:18})}),c.jsx("button",{onClick:()=>Y(1),title:m("zoom-in"),children:c.jsx(Ze,{name:"zoomIn",size:18})}),c.jsx("button",{onClick:()=>b($=>!$),title:m("composer"),children:c.jsx(Ze,{name:"keyboard",size:20})}),c.jsx("button",{onClick:()=>S($=>!$),title:m("files"),children:c.jsx(Ze,{name:"folder",size:20})})]})]}),c.jsx("div",{className:"nc-termwrap",children:c.jsx(mw,{session:e,node:t,token:o,readonly:a,takeSize:!0,sendRef:z,composerRef:Z,actionRef:G,ctrlRef:Q,setCtrlArmed:M,onFiles:k,fontSize:C,selectionMode:N,onSelectionModeChange:T,keyboardGesture:f.terminalKeyboardGesture})}),c.jsx(TE,{onKeyboard:()=>b($=>!$),onCellSwitcher:h,cellSwitcherOpen:p,send:$=>z.current($),action:$=>G.current($),ctrlArmed:B,onCtrl:L,selectionMode:N,onSelectionMode:T,keepKeyboardClosed:f.keybarKeepsKeyboardClosed,showEnter:f.showKeybarEnter,keybarLayout:f.keybarLayout}),y&&c.jsx(Cw,{submitText:$=>Z.current($),token:o,session:e,node:t,ownerId:n,keepKeyboardClosedOnVoice:f.voiceKeepsKeyboardClosed}),v&&c.jsx(gw,{session:e,node:t,token:o,filesEvent:w,onClose:()=>S(!1)})]})}function aR(){kn();const[e]=j.useState(sR),[t,n]=j.useState(e.token),[s,o]=j.useState(e.pair||""),a=j.useCallback(()=>{o("");try{sessionStorage.removeItem("nc_pair")}catch{}},[]),[u,h]=j.useState(!1),p=oR(),[f]=j.useState(()=>lN(typeof location<"u"?location.pathname:"/")),[v,S]=j.useState(f.id),y=v===Lr(null,Un),[b,w]=j.useState(null),k=ue=>{const je=ys(ue);w(je?{...je,...typeof(ue==null?void 0:ue.cellName)=="string"&&ue.cellName?{cellName:ue.cellName}:{}}:null)},[C,R]=j.useState([]),[E,F]=j.useState([]),[O,K]=j.useState([]),[Y,z]=j.useState(()=>f.ownerId?Pr():rR(f.name)),[Z,G]=j.useState(null),[Q,B]=j.useState(null),M=ue=>B(ys(ue)),[N,T]=j.useState(null),L=nf(t,p),$=j.useMemo(()=>(L||[]).filter(ue=>ue.instanceId).map(ue=>({instanceId:ue.instanceId,route:ue.route,label:ue.label,status:ue.status})),[L]),U=Vj(t,v,Y,z,$),x=U.decks;j.useEffect(()=>{const ue=new Map;for(const je of L||[]){const qe=(je.route||[je.name]).join("/"),pt=new Set((je.sessions||[]).map(_t=>_t.name));for(const _t of je.cells||[])!_t.cell||!_t.tmuxSession||_t.cell===_t.tmuxSession||pt.has(_t.cell)||ue.set(`${qe}:${_t.cell}`,`${qe}:${_t.tmuxSession}`)}z(je=>Ex(je,ue))},[L]),j.useEffect(()=>{U.localNodeId&&z(ue=>{const je=Oh(ue,U.localNodeId,$);return JSON.stringify(je)===JSON.stringify(ue)?ue:je})},[$,U.localNodeId]);const[I,D]=j.useState(null),[A,W]=j.useState(null),X=j.useRef(0),[ie,le]=j.useState(!1),[fe,we]=j.useState(nR),[pe,_e]=j.useState(!y),[He,ht]=j.useState(()=>y?localStorage.getItem(H_)==="1":!0),[Ye,Ae]=j.useState(!1),[Ne,ut]=j.useState("nodes"),[Pe,Ut]=j.useState(!1),[Rt,Dt]=j.useState(""),[vt,ft]=j.useState(!1),dt=(ue="nodes",je=!1,qe="")=>{ut(ue),Ut(je),Dt(qe),Ae(!0)},[re,xe]=j.useState(!1),[Oe,Fe]=j.useState({deviceDefault:"",localNodeId:"",localNameDefault:""}),[de,ze]=j.useState(!1);j.useEffect(()=>{try{localStorage.setItem(_b,String(fe))}catch{}},[fe]),j.useEffect(()=>{try{localStorage.setItem(H_,He?"1":"")}catch{}},[He]),j.useEffect(()=>{if(!t)return;let ue=!1;return Promise.all([ey(t),Ht("/api/config",t).then(je=>je.json())]).then(([je,qe])=>{ue||(Fe({deviceDefault:je.deviceName||"",localNodeId:je.nodeId||"",localNameDefault:je.localName||""}),ze(!!qe.readonlyDefault),(je.firstRun===!0&&!qe.readonlyDefault||s)&&xe(!0))}).catch(()=>{}),()=>{ue=!0}},[t,s]);const q=j.useCallback(async()=>{try{const je=await(await Ht("/api/sessions",t)).json();je.error||R(je.sessions||[])}catch{}try{const ue=await ws(t);F(ue.available?ue.cells||[]:[]),K(ue.available?ue.capabilities||[]:[])}catch{F([]),K([])}},[t]);j.useEffect(()=>{if(!p)return;q();const ue=setInterval(q,4e3);return()=>clearInterval(ue)},[p,q]),j.useEffect(()=>{let ue=!1;return Ht("/api/config",t).then(je=>je.json()).then(je=>{ue||Xj(je.version,je.uiVersion,"0.8.52-rc.13")}).catch(()=>{}),()=>{ue=!0}},[t]);const te=new Set([...C.map(ue=>ue.name),...L.flatMap(ue=>ue.sessions.map(je=>je.key))]),H=ki(Y),ae=ue=>z(je=>{const qe=Pw(ue,U.localNodeId,$)||ue,pt=_y(je,qe);return pt===je&&ki(je).length>=9&&U.setError(m("grid-full")),pt}),ce=async(ue,je=[])=>{try{await Xh(t,ue,je)}catch{return}const qe=je.length?`${je.join("/")}:${ue}`:ue;z(pt=>Hl(pt,qe)),q()},me=async(ue,je,qe=[])=>{try{await Z_(t,ue,je,qe)}catch{return}q()},Te=async(ue,je,qe=[])=>{await X_(t,{cell:ue,enabled:!!je},qe),q()},ve=async ue=>{if(!I)return;const{cell:je}=I,qe=Array.isArray(I.route)?I.route:[];if(ue.action==="up"){const _t=await Yh(t,{cell:je,boot:!!ue.boot,...ue.engine?{engine:ue.engine}:{},...ue.model!==void 0?{model:ue.model}:{},...ue.permissionPolicy?{permissionPolicy:ue.permissionPolicy}:{}},qe),bs=uf(_t);bs&&U.setError(bs.text)}else await Il(t,{cell:je,boot:!!ue.boot},qe);const pt=ue.action==="up"?!!ue.boot:ue.boot?!1:!!I.boot;W({id:++X.current,cell:je,route:qe,enabled:pt}),q()},Ce=j.useCallback(ue=>{W(je=>(je==null?void 0:je.id)===ue?null:je)},[]),nt=async ue=>{if(!(!(ue!=null&&ue.direct)||ie)){le(!0);try{await ef(t,ue.name,ue.tunnelStatus==="up"?"down":"up")}finally{le(!1)}}},ot=async(ue,je)=>{const qe=String(je||"").trim();return!(ue!=null&&ue.direct)||!Mr(qe)?!1:(await iy(t,ue.name,qe),!0)},Je=ue=>{const je=x.find(qe=>qe.id===ue);if(!je)return!1;try{const qe=window.open(fd(je,t),"_blank");return qe&&(qe.opener=null),!!qe}catch{return!1}},Mt=async ue=>{if(!ue||ue===v)return;const je=await U.select(ue),qe=U.records.find(pt=>pt.id===ue);S(ue),z(je),G(null),B(null);try{history.replaceState(null,"",fd(qe||ue,null))}catch{}},lt=async(ue,je)=>{const qe=await U.add(ue,je);await Mt(qe.id)},et=async(ue,je)=>{const qe=await U.rename(ue,je);if(ue===v){S(qe.id),z(Oh(qe.layout,U.localNodeId,$)),G(null),B(null);try{history.replaceState(null,"",fd(qe,null))}catch{}}},Zt=async ue=>{await U.remove(ue),ue===v&&await Mt(U.localMainId)},ni=async(ue,je)=>{!je||je===v||(await U.addTileTo(je,ue),z(qe=>Hl(qe,ue)))};if(!t)return c.jsxs("div",{className:"nc-auth",children:[c.jsx("p",{children:m("auth-prompt")}),c.jsx("input",{onChange:ue=>n(ue.target.value.trim()),placeholder:"token"}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:u,onChange:ue=>h(ue.target.checked)})," ",m("remember-device")]}),c.jsx("button",{onClick:()=>{(u?localStorage:sessionStorage).setItem("nc_token",t)},children:"ok"})]});const Wn=c.jsxs(c.Fragment,{children:[Ye&&c.jsx(Rj,{token:t,initialTab:Ne,initialLocation:Rt,startNewCell:Pe,onClose:()=>{Ae(!1),Ut(!1),Dt("")}}),re&&c.jsx(Pj,{token:t,initialPair:s,...Oe,onPairDone:a,onDone:()=>xe(!1)}),c.jsx(Ij,{token:t})]});if(!p)return N?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-single-overlay",children:c.jsx(F_,{peer:N,token:t,onBack:()=>T(null)})}),Wn]}):b?c.jsxs(c.Fragment,{children:[c.jsx(K_,{session:b.session,node:b.node,ownerId:b.ownerId,cellName:b.cellName,token:t,readonly:de,onBack:()=>w(null),onCellSwitcher:()=>ft(!0),cellSwitcherOpen:vt}),vt&&c.jsx(Hj,{token:t,current:b,onPick:ue=>{k(ue),ft(!1)},onClose:()=>ft(!1)}),Wn]}):c.jsxs(c.Fragment,{children:[c.jsx(Wx,{onPick:k,token:t,onSettings:dt,onOpenVlSession:T}),Wn]});const ui=y||!pe;return c.jsxs("div",{className:"nc-workspace",children:[ui&&c.jsx(XE,{sessions:C,cells:E,activeSessions:H,nodeGroups:L,fleetCapabilities:O,bootSettlement:A,onBootSettlementApplied:Ce,localNodeId:U.localNodeId,onPick:M,onAddTile:ae,onPower:D,onBoot:Te,onBootError:ue=>U.setError(String((ue==null?void 0:ue.message)||ue)),onNodePower:nt,onNodeRename:ot,onKill:ce,onVisibility:me,onNew:()=>dt("fleet",!0),onSettings:dt,onOpenVlSession:T,width:fe,collapsed:He,onResize:we,onToggleCollapse:()=>ht(ue=>!ue)}),c.jsxs("div",{className:"nc-workspace-main",children:[c.jsx(LN,{decks:x,currentDeck:v,onCreate:lt,onRename:et,onDelete:Zt,onReorder:U.reorder,onOpenWindow:Je,onNavigate:Mt,saveState:U.saveState,error:U.error,sidebarVisible:ui,onToggleSidebar:y?null:()=>_e(ue=>!ue)}),c.jsx(iN,{layout:Y,onLayoutChange:z,token:t,readonly:de,sessionsAlive:te,focusSession:Z,onFocus:G,onOpenSingle:M,decks:x,currentDeck:v,onSendToDeck:ni,cells:E,nodeGroups:L})]}),N&&c.jsx("div",{className:"nc-single-overlay",children:c.jsx(F_,{peer:N,token:t,onBack:()=>T(null)})}),Q&&c.jsx("div",{className:"nc-single-overlay",children:c.jsx(K_,{session:Q.session,node:Q.node,ownerId:Q.ownerId,cellName:Cf({session:Q.session,node:Q.node,ownerId:Q.ownerId,cells:E,nodeGroups:L}),token:t,readonly:de,onBack:()=>B(null)})}),I&&c.jsx(Bl,{cell:I,token:t,route:Array.isArray(I.route)?I.route:[],onConfirm:ve,onClose:()=>D(null)}),Wn]})}function lR(){kn();const e=j.useSyncExternalStore(Gj,$_,$_);if(!e.needed)return null;const t=e.kind==="install"?m("install-version-mismatch").replace("{v}",e.version):m("update-available").replace("{v}",e.version).replace(" "," ").trim();return c.jsxs("div",{className:"nc-update",role:"status","aria-live":"polite",children:[c.jsx("span",{className:"nc-update-msg",children:t}),e.kind==="reload"&&c.jsx("button",{className:"nc-update-btn",onClick:Zj,children:m("reload")})]})}Jj();h0.createRoot(document.getElementById("root")).render(c.jsxs(r0.StrictMode,{children:[c.jsx(aR,{}),c.jsx(lR,{})]}));
|
|
93
|
+
`],{type:"application/json"}),$=URL.createObjectURL(L),U=document.createElement("a");U.href=$,U.download="nexuscrew-diagnostics.json",U.click(),URL.revokeObjectURL($)}catch{y(m("diagnostics-export-failed"))}};return c.jsxs("div",{className:"nc-set-tab nc-diagnostics",children:[c.jsxs("label",{className:"nc-field",children:[m("diagnostics-target"),c.jsx("select",{value:u.key,onChange:L=>a(L.target.value),children:s.map(L=>c.jsxs("option",{value:L.key,children:[L.label,L.status!=="up"?` · ${L.status}`:""]},L.key))})]}),c.jsxs("div",{className:"nc-set-form nc-diag-controls",children:[c.jsx("div",{className:"nc-sheet-label",children:m("diagnostics-verbose")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("select",{"aria-label":m("diagnostics-duration"),value:R,onChange:L=>E(Number(L.target.value)),disabled:n,children:[300,900,1800,3600].map(L=>c.jsxs("option",{value:L,children:[L/60," min"]},L))}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:n||(h==null?void 0:h.verbose)===!0,onClick:()=>M(()=>xg(e,!0,R,u.route)),children:m("enable")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||(h==null?void 0:h.verbose)!==!0,onClick:()=>M(()=>xg(e,!1,R,u.route)),children:m("stop")})]}),c.jsx("small",{className:"nc-set-hint",children:h!=null&&h.verbose?m("diagnostics-expires").replace("{time}",new Date(h.expiresAt).toLocaleTimeString()):m("diagnostics-off")})]}),c.jsxs("div",{className:"nc-diag-filters",children:[c.jsxs("select",{"aria-label":m("diagnostics-level"),value:F,onChange:L=>O(L.target.value),children:[c.jsx("option",{value:"all",children:m("diagnostics-all-levels")}),["debug","info","warn","error"].map(L=>c.jsx("option",{value:L,children:L},L))]}),c.jsx("input",{"aria-label":m("diagnostics-component"),placeholder:m("diagnostics-component"),value:K,onChange:L=>Y(L.target.value)})]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:b,onChange:L=>w(L.target.checked)})," ",m("diagnostics-pause")]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:k,onChange:L=>C(L.target.checked)})," ",m("diagnostics-autoscroll")]})]}),c.jsx("pre",{ref:Z,className:"nc-diag-log","aria-label":m("diagnostics-log"),children:B||m("diagnostics-empty")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:N,children:m("copy")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:T,children:m("diagnostics-export")}),c.jsx("button",{type:"button",className:"nc-btn danger",disabled:n,onClick:()=>{window.confirm(m("diagnostics-clear-confirm"))&&M(async()=>{const L=await q0(e,u.route);return z.current=0,v([]),L})},children:m("diagnostics-clear")})]}),S&&c.jsx("div",{className:"nc-err",children:S})]})}function jj({token:e,settings:t,readonly:n,refresh:s,roster:o,section:a,setSection:u}){const[h,p]=j.useState(null),[f,v]=j.useState(null),[S,y]=j.useState(!1),[b,w]=j.useState(!1),[k,C]=j.useState(null),[R,E]=j.useState(!0),[F,O]=j.useState(!1);j.useEffect(()=>{C(t&&t.update||null),E(!t||t.autoUpdate!==!1),O(!!(t&&t.alternateScreen))},[t]);const K=async()=>{p(null),v(null),w(!0);try{const N=await C0(e);v(N.note||"ok"),y(!1)}catch(N){p(String(N.message||N))}w(!1)},Y=async()=>{p(null),v(null),w(!0);try{const N=await $0(e);v(`${N.note||"ok"}${N.target?` (${N.target})`:""}`)}catch(N){p(String(N.message||N))}w(!1)},z=async N=>{p(null),v(null),w(!0);try{await Ud(e,{autoUpdate:N}),E(N),s&&await s()}catch(T){p(String(T.message||T))}w(!1)},Z=async N=>{p(null),v(null),w(!0);try{await Ud(e,{alternateScreen:N}),O(N),s&&await s()}catch(T){p(String(T.message||T))}w(!1)},G=async()=>{p(null),v(null),w(!0);try{C(await H0(e))}catch(N){p(String(N.message||N))}w(!1)},Q=async()=>{p(null),v(null),w(!0);try{const N=await U0(e);C(N),v(m("npm-update-restarting"))}catch(N){p(String(N.message||N))}w(!1)},B=()=>{window.confirm(m("composer-clear-confirm"))&&(p(null),WE()?(window.dispatchEvent(new Event(Dh)),v(m("composer-clear-done"))):p(m("composer-clear-failed")))},M=t&&t.service;return c.jsxs("div",{className:"nc-set-tab",children:[c.jsx("div",{className:"nc-system-tabs",role:"tablist","aria-label":m("tab-system"),children:["general","diagnostics"].map(N=>c.jsx("button",{type:"button",role:"tab","aria-selected":a===N,className:`nc-set-tabbtn${a===N?" on":""}`,onClick:()=>u(N),children:m(`system-${N}`)},N))}),a==="general"&&c.jsxs(c.Fragment,{children:[c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-this-node")}),t&&c.jsxs("div",{className:"nc-set-info",children:["v",t.version," · ",t.platform," · :",t.port,c.jsx("br",{}),M&&M.installed?m("service-installed"):m("service-missing"),M&&M.installed?` · ${M.active?m("service-active"):m("service-inactive")}`:"",M?` · boot ${M.boot?"on":"off"}`:""]}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||b,title:n?m("settings-readonly"):"",onClick:Y,children:m("service-regenerate")})}),c.jsx(xj,{token:e,readonly:n}),c.jsx(kj,{})]}),c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-updates")}),c.jsxs("div",{className:"nc-set-form nc-update-settings",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:R,disabled:n||b||k&&!k.supported,onChange:N=>z(N.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:m("npm-auto-update")}),c.jsx("small",{children:m("npm-auto-update-help")})]})]}),k&&c.jsxs("div",{className:"nc-set-info",children:[m("npm-update-current")," v",k.current,k.latest?` · ${m("npm-update-latest")} v${k.latest}`:""," · ",m(`npm-update-${k.supported?k.phase:"unsupported"}`)]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||b||!k||!k.supported,onClick:G,children:m("npm-update-check")}),k&&k.available&&c.jsx("button",{type:"button",className:"nc-btn primary",disabled:n||b||k.phase==="installing",onClick:Q,children:m("npm-update-install")})]}),k&&k.lastError&&c.jsx("div",{className:"nc-err",children:k.lastError})]})]}),c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-security")}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:n||b,title:n?m("settings-readonly"):"",onClick:()=>{p(null),v(null),y(!0)},children:m("token-rotate")})})]}),c.jsxs("section",{className:"nc-system-group",children:[c.jsx("h3",{children:m("system-local-data")}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:m("composer-clear-data")}),c.jsx("small",{className:"nc-set-hint",children:m("composer-clear-data-help")}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:B,children:m("composer-clear-data")})})]})]})]}),a==="diagnostics"&&c.jsxs("section",{className:"nc-system-group nc-system-diagnostics",children:[c.jsx("h3",{children:m("system-diagnostics")}),c.jsxs("div",{className:"nc-set-form nc-alternate-screen-settings",children:[c.jsx("div",{className:"nc-sheet-label",children:m("system-terminal")}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox","aria-label":m("alternate-screen"),checked:F,disabled:n||b,onChange:N=>Z(N.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:m("alternate-screen")}),c.jsx("small",{children:m("alternate-screen-help")})]})]})]}),c.jsx(Nj,{token:e,roster:o,readonly:n})]}),S&&c.jsxs("div",{className:"nc-set-confirm",children:[c.jsx("div",{children:m("token-rotate-explain")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:b,onClick:()=>y(!1),children:m("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:b,onClick:K,children:m("confirm")})]})]}),f&&c.jsx("div",{className:"nc-set-note",children:f}),h&&c.jsx("div",{className:"nc-err",children:h})]})}function Rj({token:e,onClose:t,initialTab:n="nodes",initialLocation:s="",startNewCell:o=!1}){kn();const a=n==="diagnostics"?"diagnostics":"general",[u,h]=j.useState(n==="diagnostics"?"system":n),p=j.useRef(null),f=j.useRef(null),[v,S]=j.useState({start:!1,end:!1}),[y,b]=j.useState(a),[w,k]=j.useState(null),[C,R]=j.useState([]),[E,F]=j.useState([]),[O,K]=j.useState(!1),[Y,z]=j.useState(null),[Z,G]=j.useState(0),Q=nf(e,!0,Z),B=j.useCallback(()=>{const N=p.current;if(!N)return;const T=N.scrollWidth>N.clientWidth+1,L={start:T&&N.scrollLeft>1,end:T&&N.scrollLeft+N.clientWidth<N.scrollWidth-1};S($=>$.start===L.start&&$.end===L.end?$:L)},[]);j.useEffect(()=>{const N=p.current;if(!N)return;const T=typeof ResizeObserver=="function"?new ResizeObserver(B):null;return T==null||T.observe(N),window.addEventListener("resize",B),B(),()=>{T==null||T.disconnect(),window.removeEventListener("resize",B)}},[B]),j.useEffect(()=>{var T,L;(L=(T=f.current)==null?void 0:T.scrollIntoView)==null||L.call(T,{behavior:"smooth",block:"nearest",inline:"nearest"});const N=window.requestAnimationFrame?window.requestAnimationFrame(B):setTimeout(B,0);return()=>{window.cancelAnimationFrame?window.cancelAnimationFrame(N):clearTimeout(N)}},[u,B]);const M=j.useCallback(async()=>{var N;try{const T=await ey(e);k(T),z(null)}catch(T){z(String(T.message||T))}try{const L=(await x0(e)).peers||[];let $=[],U=[];try{const[x,I]=await Promise.all([Ht("/api/config",e).then(X=>X.json()).catch(()=>null),ny(e).catch(()=>null)]),D=x&&typeof x.instanceId=="string"?x.instanceId:"",A=[{instanceId:D||null,route:[],label:null},...dy(I,D)],W=await Promise.all(A.map(async X=>{try{return{ok:!0,peers:((await ty(e,X.route)).nodes||[]).map(fe=>uy(fe,X)).filter(Boolean)}}catch(ie){return{ok:!1,owner:X,error:ie}}}));for(const X of W){if(X.ok){$.push(...X.peers);continue}X.owner.route.length!==0&&U.push({instanceId:X.owner.instanceId,label:X.owner.label,route:X.owner.route,failure:/timeout/i.test(String(((N=X.error)==null?void 0:N.message)||X.error))?"timeout":"unreachable"})}}catch{}F(U),R([...L,...$])}catch(T){z(String(T.message||T))}},[e]);return j.useEffect(()=>{M();const N=setInterval(M,5e3);return()=>clearInterval(N)},[M]),j.useEffect(()=>{let N=!1;return Ht("/api/config",e).then(T=>T.json()).then(T=>{N||K(!!T.readonlyDefault)}).catch(()=>{}),()=>{N=!0}},[e]),c.jsx("div",{className:"nc-set-overlay",onClick:t,children:c.jsxs("div",{className:"nc-set-panel",onClick:N=>N.stopPropagation(),children:[c.jsxs("div",{className:"nc-set-head",children:[c.jsx(Ze,{name:"gear",size:18}),c.jsx("b",{children:m("settings")}),c.jsx("button",{type:"button",className:"nc-set-close",onClick:t,title:m("close"),children:c.jsx(Ze,{name:"x",size:18})})]}),O&&c.jsx("div",{className:"nc-set-readonly",children:m("settings-readonly")}),Y&&c.jsx("div",{className:"nc-err",children:Y}),c.jsx("div",{className:`nc-set-tabs-wrap${v.start?" has-start-overflow":""}${v.end?" has-end-overflow":""}`,children:c.jsx("div",{className:"nc-set-tabs",ref:p,onScroll:B,children:["nodes","fleet","audio","input","system"].map(N=>c.jsx("button",{ref:u===N?f:null,type:"button",className:`nc-set-tabbtn${u===N?" on":""}`,onClick:()=>h(N),children:m(`tab-${N}`)},N))})}),c.jsxs("div",{className:"nc-set-body",children:[u==="nodes"&&c.jsx(Sj,{token:e,nodes:C,roster:Q,settings:w,readonly:O,vlUnavailable:E,refresh:M,refreshAliases:()=>G(N=>N+1)}),u==="fleet"&&c.jsx(ij,{token:e,readonly:O,startNewCell:o,initialLocation:s,targets:Q.map(N=>({route:N.route,label:N.label||N.name,status:N.status}))}),u==="audio"&&c.jsx(Cj,{token:e,readonly:O,nodes:C,settings:w}),u==="input"&&c.jsx(Ej,{}),u==="system"&&c.jsx(jj,{token:e,settings:w,readonly:O,refresh:M,roster:Q,section:y,setSection:b})]})]})})}function Pj({token:e,initialPair:t,deviceDefault:n="",localNodeId:s="",localNameDefault:o="",onPairDone:a,onDone:u}){kn();const[h,p]=j.useState(t?"pair":"welcome"),[f,v]=j.useState(!1),[S,y]=j.useState(null),b=async()=>{v(!0),y(null);try{await Ud(e,{wizardDone:!0}),a&&a(),u()}catch(w){y(String(w.message||w)),v(!1)}};return c.jsx("div",{className:"nc-wiz-overlay",children:c.jsxs("div",{className:"nc-wiz",children:[c.jsxs("div",{className:"nc-wiz-head",children:[c.jsx("b",{children:m("wizard-title")}),c.jsx("small",{children:m("hydra-simple")})]}),h==="welcome"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:m("local-ready")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:b,children:m("local-only")}),c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:()=>p("pair"),children:m("add-node")})]})]}),h==="pair"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx(Yw,{token:e,initial:t||"",autoStart:!!t,deviceDefault:n,localNodeId:s,localNameDefault:o,onBusyChange:v,onSuccess:async()=>{a&&a(),p("done")}}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:()=>{a&&a(),p("welcome")},children:m("back")})})]}),h==="done"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:m("node-connected")}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:b,children:m("finish")})})]}),S&&c.jsx("div",{className:"nc-err",children:S})]})})}function Lj(e,t){if(typeof EventSource>"u"||!e)return()=>{};const n=new EventSource(`/api/events?token=${encodeURIComponent(e)}`);return n.onmessage=s=>{try{const o=JSON.parse(s.data);o&&typeof o=="object"&&typeof o.type=="string"&&t(o)}catch{}},()=>{try{n.close()}catch{}}}const Aj=6e3,Tj=12e3;function Dj({n:e,onClose:t}){return c.jsxs("div",{className:`nc-ntf-toast${e.urgency==="high"?" high":""}`,role:"status","aria-live":"polite",children:[c.jsxs("div",{className:"nc-ntf-toast-txt",children:[c.jsx("b",{children:e.title}),e.body&&c.jsx("small",{children:e.body}),e.session&&c.jsx("span",{className:"nc-ntf-from",children:e.session})]}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:t,title:m("close"),children:c.jsx(Ze,{name:"x",size:14})})]})}function Mj({ask:e,token:t,onAnswered:n,onDismiss:s}){const[o,a]=j.useState(""),[u,h]=j.useState(!1),[p,f]=j.useState(null),[v,S]=j.useState(!1),y=async w=>{const k=String(w||"").trim();if(!(!k||u)){f(null),h(!0);try{await Y0(t,e.id,k),n(e.id)}catch(C){f(String(C.message||C))}h(!1)}},b=async()=>{if(!v){S(!0);try{await G0(t,e.id),s(e.id)}catch{S(!1)}}};return c.jsxs("div",{className:"nc-ask-card",children:[c.jsxs("div",{className:"nc-ask-head",children:[c.jsx("span",{className:"nc-ntf-from",children:e.session}),c.jsxs("code",{className:"nc-ask-id",children:["#",e.id]}),c.jsx("button",{type:"button",className:"nc-ask-dismiss",title:m("ask-dismiss"),"aria-label":m("ask-dismiss"),disabled:v,onClick:b,children:c.jsx(Ze,{name:"x",size:14})})]}),c.jsx("div",{className:"nc-ask-q",children:e.question}),Array.isArray(e.options)&&e.options.length>0&&c.jsx("div",{className:"nc-ask-opts",children:e.options.map(w=>c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:u,onClick:()=>y(w),children:w},w))}),c.jsxs("div",{className:"nc-ask-reply",children:[c.jsx("textarea",{rows:2,placeholder:m("ask-reply-ph"),value:o,disabled:u,onChange:w=>a(w.target.value)}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:u||!o.trim(),onClick:()=>y(o),children:m("send")})]}),p&&c.jsx("div",{className:"nc-err",children:p})]})}function Ij({token:e}){const[t]=kn(),[n]=ub(),[s,o]=j.useState([]),[a,u]=j.useState([]),[h,p]=j.useState(()=>{try{return/(?:^|[#&])ask=/.test(location.hash)}catch{return!1}}),f=j.useRef(0),v=j.useRef(null);v.current||(v.current=yj());const S=j.useRef({enabled:n,lang:t});S.current={enabled:n,lang:t};const y=j.useCallback(C=>{o(R=>R.filter(E=>E.key!==C))},[]),b=j.useCallback(C=>{const R=`t${f.current+=1}`;o(F=>[...F.slice(-3),{...C,key:R}]),setTimeout(()=>y(R),C.urgency==="high"?Tj:Aj);const E=S.current;if(E.enabled)try{v.current.enqueue(C,pj(C,E.lang))}catch{}},[y]);j.useEffect(()=>{n||v.current.stop()},[n]),j.useEffect(()=>{const C=()=>{(document.visibilityState!=="visible"||!document.hasFocus())&&v.current.stop()},R=()=>v.current.stop();return document.addEventListener("visibilitychange",C),window.addEventListener("blur",C),window.addEventListener(Fh,R),()=>{document.removeEventListener("visibilitychange",C),window.removeEventListener("blur",C),window.removeEventListener(Fh,R),typeof v.current.dispose=="function"?v.current.dispose():v.current.stop()}},[]),j.useEffect(()=>{if(!e)return;let C=!1;V0(e).then(E=>{C||u(E.asks||[])}).catch(()=>{});const R=Lj(e,E=>{E.type==="notify"?b(E):E.type==="ask"&&E.ask&&E.ask.id?u(F=>F.some(O=>O.id===E.ask.id)?F:[...F,E.ask]):E.type==="ask-answered"&&E.id?u(F=>F.filter(O=>O.id!==E.id)):E.type==="ask-dismissed"&&E.id&&u(F=>F.filter(O=>O.id!==E.id))});return()=>{C=!0,R()}},[e,b]);const w=C=>u(R=>R.filter(E=>E.id!==C)),k=C=>u(R=>R.filter(E=>E.id!==C));return c.jsxs(c.Fragment,{children:[s.length>0&&c.jsx("div",{className:"nc-ntf-toasts",children:s.map(C=>c.jsx(Dj,{n:C,onClose:()=>y(C.key)},C.key))}),a.length>0&&!h&&c.jsxs("button",{type:"button",className:"nc-ask-badge",onClick:()=>p(!0),title:m("asks-title"),children:["? ",c.jsx("span",{className:"nc-ask-count",children:a.length})]}),a.length>0&&h&&c.jsxs("div",{className:"nc-ask-panel",children:[c.jsxs("div",{className:"nc-ask-panel-head",children:[c.jsx("b",{children:m("asks-title")}),c.jsx("span",{className:"nc-ask-count",children:a.length}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:()=>p(!1),title:m("close"),children:c.jsx(Ze,{name:"x",size:16})})]}),c.jsx("div",{className:"nc-ask-panel-body",children:a.map(C=>c.jsx(Mj,{ask:C,token:e,onAnswered:w,onDismiss:k},C.id))})]})]})}const Oj=4e3;async function Bj(e){const t=await Ht("/api/sessions",e);if((t==null?void 0:t.ok)===!1)throw new Error(`HTTP ${t.status}`);const n=await t.json();if(!Array.isArray(n==null?void 0:n.sessions))throw new Error("invalid sessions payload");return n.sessions}async function Fd(e,t=[]){var h;const[n,s]=await Promise.allSettled([t.length?Jh(e,t):Bj(e),ws(e,t)]),o=n.status==="fulfilled"&&Array.isArray((h=n.value)==null?void 0:h.sessions)?n.value.sessions:Array.isArray(n.value)?n.value:null,a=s.status==="fulfilled"?s.value:null,u=(a==null?void 0:a.available)===!0&&Array.isArray(a.cells)?a.cells:null;return{sessions:o,cells:u,fresh:Array.isArray(o)&&Array.isArray(u)}}function db(e,t,n){return n===!0&&(e==null?void 0:e.degraded)!==!0&&(e==null?void 0:e.active)===!0&&e.tmux!==!1&&!!e.tmuxSession&&(t||[]).some(s=>(s==null?void 0:s.name)===e.tmuxSession)}function zj(e){const t=new Map((e.sessions||[]).map(o=>[o.name,o])),n=[],s=(o,a,u,h=[],p="")=>{const f=new Map((a||[]).map(v=>[v.name,v]));for(const v of o||[]){const S=f.get(v.tmuxSession)||{},y=of(v,S),b=db(v,a,u);n.push({key:Go(h,v.tmuxSession||v.cell),session:v.tmuxSession,route:h,cellName:v.cell,label:v.cell,node:h.length?h.join("/"):"",nodeLabel:p,live:b,selectable:b,verified:u===!0,working:y.working,degraded:!!v.degraded,active:v.active===!0,activity:S.activity||v.activity||0,subtitle:y.subtitle})}};s(e.cells,[...t.values()],e.localFresh===!0);for(const o of e.nodeGroups||[]){const a=Array.isArray(o.route)?o.route:[];o.kind==="vl"||!a.length||s(o.cells,o.sessions,o.switcherFresh===!0,a,o.label||o.name||"")}return n}function Fj(e){const t=new Map,n=Array.isArray(e.sessions)?e.sessions:[],s=Array.isArray(e.cells)?e.cells:[],o=new Map(n.map(u=>[u.name,u])),a=new Set(s.map(u=>u.tmuxSession).filter(Boolean));t.set("local",af(s,n.filter(u=>!a.has(u.name)),o));for(const u of e.nodeGroups||[]){const h=Array.isArray(u.route)?u.route:[];if(u.kind==="vl"||!h.length)continue;const p=Array.isArray(u.cells)?u.cells:[],f=Array.isArray(u.sessions)?u.sessions:[],v=new Set(p.map(S=>S.tmuxSession).filter(Boolean));t.set(h.join("/"),Fl({...u,route:h,cells:p,sessions:f,unmanaged:f.filter(S=>!v.has(S.name))}).rawItems)}return t}function $j(e,t,n,s){return[...new Set(e.map(a=>a.node||"local"))].flatMap(a=>{const u=e.filter(v=>(v.node||"local")===a),h=new Map((t.get(a)||[]).map(v=>[v.key,v])),p=new Map(u.map(v=>[v.key,v])),f=u.map(v=>{const S=h.get(v.key);return{...v,label:(S==null?void 0:S.label)||v.label,live:(S==null?void 0:S.live)??v.live,fresh:(S==null?void 0:S.fresh)===!0,activity:(S==null?void 0:S.activity)??v.activity}});return gs(f,n,"all",Fi(s,a)).map(v=>p.get(v.key))})}function Hj({token:e,current:t,onPick:n,onClose:s}){const[o,a]=j.useState(Ig),[u,h]=j.useState(!1),[p,f]=j.useState(!1),[v,S]=j.useState(""),[y,b]=j.useState(""),[w,k]=j.useState(""),C=j.useRef(null),R=j.useRef(null),E=j.useMemo(()=>zj(o),[o]),F=j.useMemo(()=>Fj(o),[o]),{pins:O,orders:K,canMoveRoster:Y,moveRoster:z,stepRoster:Z}=rf(),G=j.useMemo(()=>$j(E,F,O,K),[E,F,O,K]),Q=j.useMemo(()=>u?G:G.filter(L=>L.selectable||L.degraded&&L.active),[G,u]),B=j.useMemo(()=>E.find(L=>L.key===y&&L.selectable),[E,y]);j.useEffect(()=>{var U;const L=document.activeElement;(U=R.current)==null||U.focus();const $=x=>{x.key==="Escape"&&s()};return document.addEventListener("keydown",$),()=>{var x;document.removeEventListener("keydown",$),(x=L==null?void 0:L.focus)==null||x.call(L)}},[s]),j.useEffect(()=>{let L=!0,$=!1;const U=async()=>{if($)return;$=!0;const I=Ig(),D=Array.isArray(I.nodeGroups)?I.nodeGroups:[],A=Fd(e),W=pe=>pe.kind!=="vl"&&Array.isArray(pe.route)&&pe.route.length>0,X=await Promise.all(D.filter(W).map(async pe=>({group:pe,result:await Fd(e,pe.route)}))),ie=await A;if(!L)return;const le=new Map(X.map(({group:pe,result:_e})=>[JSON.stringify(pe.route||[]),_e])),fe=D.map(pe=>{if(!W(pe))return{...pe,switcherFresh:!1};const _e=le.get(JSON.stringify(pe.route||[]));return _e?{...pe,sessions:_e.sessions||pe.sessions||[],cells:_e.cells||pe.cells||[],switcherFresh:_e.fresh}:{...pe,switcherFresh:!1}}),we=by({...I,sessions:ie.sessions||I.sessions||[],cells:ie.cells||I.cells||[],localFresh:ie.fresh,nodeGroups:fe,refreshedAt:Date.now()});a(we),f(!0),$=!1};U();const x=setInterval(U,Oj);return()=>{L=!1,clearInterval(x)}},[e]);const M=L=>L.degraded?m("cell-degraded"):L.verified?L.selectable?L.working?m("cell-working"):m("cell-idle"):m("cell-off"):m("cell-switcher-not-confirmed"),N=L=>{if(S(""),!L.selectable){S(m("cell-switcher-not-active"));return}b(L.key)},T=async()=>{const L=B;if(!L){S(m("cell-switcher-select"));return}S(""),k(L.key);try{const $=await Fd(e,L.route),U=($.cells||[]).find(x=>(x==null?void 0:x.cell)===L.cellName&&(x==null?void 0:x.tmuxSession)===L.session);if(!db(U,$.sessions,$.fresh)){S(m("cell-switcher-not-active"));return}n({session:L.session,...L.node?{node:L.node}:{},cellName:L.cellName}),s()}catch{S(m("cell-switcher-not-active"))}finally{k("")}};return c.jsx("div",{className:"nc-cell-switcher-backdrop",onClick:s,children:c.jsxs("aside",{ref:C,className:"nc-cell-switcher",role:"dialog","aria-label":m("fleet-cells"),tabIndex:-1,onClick:L=>L.stopPropagation(),children:[c.jsxs("div",{className:"nc-cell-switcher-list",children:[!p&&c.jsx("div",{className:"nc-empty",role:"status",children:m("cell-switcher-refreshing")}),p&&Q.length===0&&c.jsx("div",{className:"nc-empty",role:"status",children:m("cell-switcher-empty-active")}),Q.map(L=>{const $=(t==null?void 0:t.session)===L.session&&((t==null?void 0:t.node)||"")===L.node,U=y===L.key,x=M(L),I=L.node||"local",D=F.get(I)||E.filter(A=>(A.node||"local")===I);return c.jsxs("div",{className:`nc-cell-switcher-row${$?" current":""}${U?" selected":""}${L.selectable?"":" off"}`,"data-roster-key":L.key,"data-position":I,children:[c.jsx(bi,{position:I,itemKey:L.key,label:L.cellName,canMove:Y,onMove:(A,W)=>z(I,A,W,D),onStep:A=>Z(I,L.key,A,D)}),c.jsxs("button",{type:"button",className:"nc-cell-switcher-row-select","aria-current":$?"true":void 0,"aria-pressed":U,"aria-disabled":!L.selectable,disabled:w===L.key,onClick:()=>N(L),children:[c.jsx("span",{className:`nc-cell-switcher-dot${L.degraded?" warn":L.live?` on${L.working?" working":""}`:""}`}),c.jsxs("span",{className:"nc-cell-switcher-copy",children:[c.jsx("b",{children:L.cellName}),c.jsx("small",{className:"nc-cell-switcher-state",children:x}),c.jsx("small",{children:[L.nodeLabel,L.subtitle].filter(Boolean).join(" · ")})]})]})]},L.key)})]}),v&&c.jsx("div",{className:"nc-cell-switcher-notice",role:"status",children:v}),c.jsx("div",{className:"nc-cell-switcher-actions",children:c.jsx("button",{type:"button",className:"nc-cell-switcher-open",disabled:!B||!!w,onClick:T,children:B?`${m("cell-switcher-open")}: ${B.cellName}`:m("cell-switcher-select")})}),c.jsxs("div",{className:"nc-cell-switcher-controls",children:[c.jsx("b",{children:m("fleet-cells")}),c.jsx("button",{type:"button",className:"nc-cell-switcher-filter","aria-pressed":u,onClick:()=>{S(""),h(L=>!L)},children:m(u?"cell-switcher-show-active":"cell-switcher-show-all")}),c.jsx("button",{ref:R,type:"button",className:"nc-cell-switcher-close","aria-label":m("cell-switcher-close"),title:m("cell-switcher-close"),onClick:s,children:"×"})]})]})})}const Uj=2e3;function Wj(e){if(e.kind==="gap")return`${m("vl-events-gap")} ${e.count||"?"}`;if(e.kind==="truncate")return m("vl-events-truncated");if(e.kind==="turn_end")return m("vl-events-turn-end");if(e.kind==="done")return m("vl-events-done");if(e.kind==="writer_epoch")return m("vl-events-writer-epoch");if(e.kind==="tool_start")return`${m("vl-events-tool")} ${e.name||""}`.trim();if(e.kind==="tool_end"){const t=e.isError?m("vl-events-tool-failed"):m("vl-events-tool-ok");return`${m("vl-events-tool")} ${e.name||""} — ${t}`.trim()}return e.kind==="usage"?e.text||m("vl-events-usage"):e.text||""}function Kj({token:e,nodeId:t,route:n=[]}){const[s,o]=j.useState([]),[a,u]=j.useState(!1),h=j.useRef(0),p=j.useRef(!1);return j.useEffect(()=>{let f=!0;h.current=0,p.current=!1,o([]);const v=async()=>{if(!p.current){p.current=!0;try{const b=await k0(e,t,h.current,n);if(!f)return;u(!1);const k=(Array.isArray(b==null?void 0:b.events)?b.events:[]).filter(C=>Number.isSafeInteger(C==null?void 0:C.seq)&&C.seq>h.current);k.length&&(o(C=>C.concat(k).slice(-500)),h.current=k[k.length-1].seq),Number.isSafeInteger(b==null?void 0:b.cursor)&&b.cursor>h.current&&(h.current=b.cursor)}catch{f&&u(!0)}finally{p.current=!1}}},S=()=>{v().catch(()=>{})};S();const y=setInterval(S,Uj);return()=>{f=!1,clearInterval(y)}},[e,t,JSON.stringify(n)]),c.jsxs("div",{className:"nc-vl-events",children:[a&&c.jsx("div",{className:"nc-set-hint nc-vl-events-stale",children:m("vl-events-stale")}),s.length===0&&!a&&c.jsx("small",{className:"nc-set-hint","data-testid":"vl-events-empty",children:m("vl-events-empty")}),s.length>0&&c.jsx("ol",{className:"nc-vl-events-list",children:s.map(f=>c.jsx("li",{"data-testid":`vl-ev-${f.seq}`,className:`nc-vl-ev nc-vl-ev-${f.kind}${f.isError?" error":""}${f.kind==="gap"?" gap":""}`,children:Wj(f)},`${f.seq}-${f.kind}`))})]})}function F_({peer:e,token:t,onBack:n}){var s;return kn(),e?c.jsxs("section",{className:"nc-vl-session-view",children:[c.jsxs("header",{className:"nc-bar nc-bar-single",children:[c.jsxs("button",{onClick:n,title:m("back"),children:[c.jsx(Ze,{name:"chevronLeft",size:18}),c.jsx("span",{className:"nc-bar-label",children:m("back")})]}),c.jsxs("span",{className:"nc-bar-center",children:[c.jsx("b",{title:e.label,children:e.label}),((s=e.session)==null?void 0:s.profile)&&c.jsxs("small",{className:"nc-bar-sub",children:[e.session.profile," · ",m("vl-events-title")]})]}),c.jsx("span",{className:"nc-bar-right"})]}),c.jsx("div",{className:"nc-vl-session-body",children:c.jsx(Kj,{token:t,nodeId:e.nodeId,route:e.route||[]})})]}):null}const $d=e=>ki(Ys(e)).length===0,Hd=e=>Array.isArray(e)?e.join("/"):"";function qj(e){const t=new Set,n=[];for(const s of Array.isArray(e)?e:[])!s||!Hn.test(String(s.instanceId||""))||!Array.isArray(s.route)||!s.route.length||t.has(s.instanceId)||(t.add(s.instanceId),n.push({instanceId:s.instanceId,route:[...s.route],label:String(s.label||s.name||s.route.join(" › ")),status:s.status||"offline"}));return n.sort((s,o)=>s.label.localeCompare(o.label))}function zo(e){return{instanceId:e.ownerId,route:e.ownerRoute||[],label:e.ownerLabel,status:e.available===!1?"offline":"up"}}function Hs(e,t,n,s=!1,o=!0){const a=t.instanceId;return{...e,id:Lr(s?null:a,e.name),ownerId:a,ownerRoute:s?[]:[...t.route],ownerLabel:s?"Local":t.label,local:s,available:o,ownerTopology:Array.isArray(n)?n:[],layout:Ih(e.layout,a,n)}}function Vj(e,t,n,s,o=[]){const[a,u]=j.useState([]),[h,p]=j.useState(!1),[f,v]=j.useState("idle"),[S,y]=j.useState(""),[b,w]=j.useState(""),k=j.useRef([]),C=j.useRef([]),R=j.useRef(""),E=j.useRef(t),F=j.useRef(n),O=j.useRef(!1),K=j.useRef(!0),Y=j.useRef(""),z=j.useMemo(()=>qj(o),[o]),Z=z.map(D=>`${D.instanceId}:${Hd(D.route)}:${D.status}:${D.label}`).join("|");C.current=z,F.current=n,E.current=t,k.current=a;const G=j.useCallback(D=>Oh(D.layout,R.current,C.current),[]),Q=j.useCallback((D,A=!0,W=E.current)=>{const X=qv(D,xl()),ie=k.current.some(fe=>fe.id===W);k.current=X,u(X);const le=X.find(fe=>fe.id===W);if(A&&le){K.current=!0;const fe=G(le);s(fe),le.local&&pN(le.name,fe)}else ie&&!le&&(K.current=!0,s(Pr()),y("deck non più condiviso dal nodo owner"));uN(X.filter(fe=>fe.local).map(fe=>fe.name))},[s,G]),B=j.useCallback(async D=>{let A=await pl(e);const W=A.decks.find(X=>X.name==="main");if(A.decks.length===1&&W&&W.revision===0&&$d(W.layout)){const X=cN(),ie=Ys(Bh("main")||Pr());if(!$d(ie)){const le=Ih(ie,D,[]);A.decks[0]=await ml(e,"main",le,0,[])}for(const le of X.filter(fe=>fe!=="main"))try{let fe=await kg(e,le,[]);const we=Ys(Bh(le)||Pr());$d(we)||(fe=await ml(e,le,Ih(we,D,[]),fe.revision,[]))}catch{}A=await pl(e)}return A},[e]),M=j.useCallback(async({migrate:D=!1}={})=>{const A=await Zh(e,[]),W=Hn.test(String(A.instanceId||""))?A.instanceId:"";if(!W)throw new Error("instanceId locale non disponibile");R.current=W,w(W);const[X,ie]=await Promise.all([D?B(W):pl(e),bg(e,[]).catch(()=>({nodes:[]}))]),le={instanceId:W,route:[],label:"Local"},fe=X.decks.map(pe=>Hs(pe,le,ie.nodes,!0,!0)),we=k.current;return await Promise.all(C.current.map(async pe=>{if(pe.status!=="up"){fe.push(...we.filter(_e=>!_e.local&&_e.ownerId===pe.instanceId).map(_e=>({..._e,ownerRoute:[...pe.route],ownerLabel:pe.label,available:!1})));return}try{const[_e,He]=await Promise.all([pl(e,pe.route),bg(e,pe.route).catch(()=>({nodes:[]}))]);fe.push(..._e.decks.map(ht=>Hs(ht,pe,He.nodes,!1,!0)))}catch{fe.push(...we.filter(He=>!He.local&&He.ownerId===pe.instanceId).map(He=>({...He,ownerRoute:[...pe.route],ownerLabel:pe.label,available:!1})))}})),fe.sort((pe,_e)=>pe.local===_e.local?pe.ownerLabel.localeCompare(_e.ownerLabel)||(pe.name==="main"?-1:_e.name==="main"?1:pe.name.localeCompare(_e.name)):pe.local?-1:1),fe},[e,B,Z]);j.useEffect(()=>{if(!e)return;let D=!1;const A=Y.current!==e;return Y.current=e,M({migrate:A}).then(W=>{if(D)return;const X=k.current.find(fe=>fe.id===E.current),ie=W.find(fe=>fe.id===E.current),le=ie&&(!X||ie.revision>X.revision||X.available!==ie.available||Hd(X.ownerRoute)!==Hd(ie.ownerRoute));Q(W,A||!!le&&!O.current),p(!0),ie&&y("")}).catch(W=>{D||(p(!0),y(String(W.message||W)))}),()=>{D=!0}},[Q,M,Z,e]);const N=j.useCallback(async(D=E.current)=>{if(!h||!O.current)return!0;const A=k.current.find(W=>W.id===D);if(!A)return y(`deck inesistente: ${D}`),!1;if(A.available===!1)return y(`nodo owner offline: ${A.ownerLabel}`),!1;v("saving");try{const W=Wv(Ys(F.current),A.ownerId,A.ownerTopology),X=await ml(e,A.name,W,A.revision,A.ownerRoute),ie=Hs(X,zo(A),A.ownerTopology,A.local,!0);return Q(k.current.map(le=>le.id===D?ie:le),!1),O.current=!1,v("saved"),y(""),setTimeout(()=>v("idle"),1500),!0}catch(W){if(v("error"),y(String(W.message||W)),W.status===409&&W.data&&W.data.current){const X=k.current.find(le=>le.id===D),ie=Hs(W.data.current,zo(X),X.ownerTopology,X.local,!0);Q(k.current.map(le=>le.id===D?ie:le),!1)}return!1}},[h,e,Q]);return j.useEffect(()=>{if(!h)return;if(K.current){K.current=!1;return}O.current=!0,v("saving");const D=setTimeout(N,650);return()=>clearTimeout(D)},[n,h,N]),j.useEffect(()=>{if(!h)return;const D=setInterval(async()=>{try{const A=await M(),W=k.current.find(le=>le.id===E.current),X=A.find(le=>le.id===E.current),ie=X&&(!W||X.revision>W.revision||W.available!==X.available);Q(A,ie&&!O.current)}catch{}},5e3);return()=>clearInterval(D)},[h,M,Q]),{decks:a,records:a,localNodeId:b,ready:h,saveState:f,error:S,setError:y,saveNow:N,select:async D=>{if(O.current&&!await N(E.current))throw new Error("salvataggio del deck corrente fallito: cambio annullato");const A=k.current.find(W=>W.id===D);if(!A)throw new Error(`deck inesistente: ${D}`);return O.current=!1,K.current=!0,G(A)},add:async(D,A=null)=>{const W=A===Si?k.current.find(le=>le.local):A?k.current.find(le=>le.ownerId===A):null;if(!W||W.available===!1)throw new Error("nodo owner non disponibile");const X=await kg(e,D,W.ownerRoute),ie=Hs(X,zo(W),W.ownerTopology,W.local,!0);return Q([...k.current,ie],!1),ie},rename:async(D,A)=>{const W=k.current.find(we=>we.id===D);if(!W)throw new Error("deck inesistente");if(D===E.current&&O.current&&!await N(D))throw new Error(`salvataggio di "${W.name}" fallito: rinomina annullata`);const X=k.current.find(we=>we.id===D);if(!X)throw new Error("deck inesistente");const ie=await X0(e,X.name,A,X.revision,X.ownerRoute),le=Hs(ie,zo(X),X.ownerTopology,X.local,!0),fe=X.local?Si:X.ownerId;return pd(hN(xl(),fe,D,le.id)),Q(k.current.map(we=>we.id===D?le:we),!1),le},remove:async D=>{const A=k.current.find(X=>X.id===D);if(!A)throw new Error("deck inesistente");D===E.current&&(O.current=!1),await Q0(e,A.name,A.revision,A.ownerRoute);const W=A.local?Si:A.ownerId;pd(fN(xl(),W,D)),Q(k.current.filter(X=>X.id!==D),!1)},reorder:(D,A)=>{const W=k.current.find(pe=>pe.id===D),X=k.current.find(pe=>pe.id===A);if(!W||!X)return!1;const ie=W.local?Si:W.ownerId,le=X.local?Si:X.ownerId;if(ie!==le)return!1;const fe=k.current.filter(pe=>(pe.local?Si:pe.ownerId)===ie).map(pe=>pe.id),we=pd(dN(xl(),ie,D,A,fe));return Q(qv(k.current,we),!1),!0},addTileTo:async(D,A)=>{const W=k.current.find(pe=>pe.id===D);if(!W)throw new Error("deck inesistente");if(W.available===!1)throw new Error(`nodo owner offline: ${W.ownerLabel}`);const X=Pw(A,R.current,C.current);if(!X)throw new Error("riferimento sessione non valido");const ie=G(W),le=Wv(_y(ie,X),W.ownerId,W.ownerTopology),fe=await ml(e,W.name,le,W.revision,W.ownerRoute),we=Hs(fe,zo(W),W.ownerTopology,W.local,!0);return Q(k.current.map(pe=>pe.id===D?we:pe),!1),we},localMainId:Lr(null,"main"),parseDeckId:sc}}const Yj="/sw.js",Hh="nc-sw-update";let Uh=null,Wh=!1,Kh=null,hb=Object.freeze({needed:!1,kind:null,version:""}),fb=!1;function pb(){typeof window<"u"&&window.dispatchEvent(new Event(Hh))}function mb(){const e=Kh||(Wh?{kind:"reload",version:""}:null);hb=Object.freeze({needed:!!e,kind:(e==null?void 0:e.kind)||null,version:(e==null?void 0:e.version)||""})}function $_(){return hb}function Gj(e){return typeof window>"u"?()=>{}:(window.addEventListener(Hh,e),()=>window.removeEventListener(Hh,e))}function Ff(e){Wh!==e&&(Wh=e,mb(),pb())}function Xj(e,t,n){let s=null;e&&t&&e!==t?s={kind:"install",version:e}:t&&n&&t!==n&&(s={kind:"reload",version:t});const o=JSON.stringify(s)===JSON.stringify(Kh);Kh=s,mb(),o||pb()}function Qj(e){e.addEventListener("statechange",()=>{e.state==="installed"&&navigator.serviceWorker.controller&&Ff(!0)})}function Jj(){typeof navigator>"u"||!("serviceWorker"in navigator)||(navigator.serviceWorker.addEventListener("controllerchange",()=>{fb&&location.reload()}),navigator.serviceWorker.register(Yj).then(e=>{Uh=e,e.waiting&&Ff(!0),e.addEventListener("updatefound",()=>{const t=e.installing;t&&Qj(t)}),setInterval(()=>{e.update().catch(()=>{})},3600*1e3)}).catch(()=>{}))}function Zj(){fb=!0,Ff(!1);const e=Uh&&Uh.waiting;e?(e.postMessage({type:"SKIP_WAITING"}),setTimeout(()=>location.reload(),4e3)):location.reload()}function eR({hash:e="",origin:t="",pathname:n="",search:s=""}={}){const o=String(e||"").replace(/^#/,""),a=`${n||""}${s||""}`;if(!o)return{token:"",pair:"",nextUrl:a};const u=new URLSearchParams(o),h=u.get("token")||"",p=u.get("pair")||"",f=p?`${t||""}${n||""}#pair=${p}`:"";return{token:h,pair:f,nextUrl:a}}const gb=9,vb=24,_b="nc_side_w",H_="nc_side_min",tR=240,U_="(min-width:1024px) and (pointer:fine)";function nR(){const e=Number(localStorage.getItem(_b));return e>=180&&e<=480?e:tR}function iR(){const e=Number(localStorage.getItem("nc_fontsize"));return e>=gb&&e<=vb?e:13}function sR(){const e={token:"",pair:""};try{const{token:t,pair:n,nextUrl:s}=eR({hash:location.hash,origin:location.origin,pathname:location.pathname,search:location.search});if(t){e.token=t;try{localStorage.setItem("nc_token",t)}catch{}}if(n){e.pair=n;try{sessionStorage.setItem("nc_pair",n)}catch{}}if(location.hash)try{history.replaceState(null,"",s)}catch{}}catch{}if(e.token||(e.token=sessionStorage.getItem("nc_token")||localStorage.getItem("nc_token")||""),!e.pair){const t=sessionStorage.getItem("nc_pair");t&&(e.pair=t)}return e}function rR(e){try{return Ys(Bh(e))}catch{return Pr()}}function W_(e){if(!e)return"";const t=Math.floor(Date.now()/1e3)-e;return t<0||t<60?"ora":t<3600?`${Math.floor(t/60)}m`:t<86400?`${Math.floor(t/3600)}h`:`${Math.floor(t/86400)}g`}function oR(){const[e,t]=j.useState(()=>window.matchMedia(U_).matches);return j.useEffect(()=>{const n=window.matchMedia(U_),s=o=>t(o.matches);return n.addEventListener("change",s),()=>n.removeEventListener("change",s)},[]),e}function K_({session:e,node:t,ownerId:n,cellName:s,token:o,readonly:a=!1,onBack:u,onCellSwitcher:h,cellSwitcherOpen:p=!1}){kn();const[f]=kf(),[v,S]=j.useState(!1),[y,b]=j.useState(()=>window.matchMedia("(pointer: coarse)").matches),[w,k]=j.useState(null),[C,R]=j.useState(iR),[E,F]=j.useState(s||e),[O,K]=j.useState(""),Y=$=>R(U=>{const x=Math.max(gb,Math.min(vb,U+$));return localStorage.setItem("nc_fontsize",String(x)),x}),z=j.useRef(()=>{}),Z=j.useRef(()=>!1),G=j.useRef(()=>{}),Q=j.useRef(!1),[B,M]=j.useState(!1),[N,T]=j.useState(!1),L=()=>{Q.current=!Q.current,M(Q.current)};return j.useEffect(()=>{F(s||e)},[s,e]),j.useEffect(()=>{let $=!0;const U=t?t.split("/"):[],x=t?`/api/route/${t.split("/").map(encodeURIComponent).join("/")}/_`:"/api";async function I(){let A=null,W=null;try{const le=await(await Ht(`${x}/sessions`,o)).json();Array.isArray(le.sessions)&&(A=le.sessions.find(fe=>fe.name===e))}catch{}try{const ie=await ws(o,U);ie.available&&Array.isArray(ie.cells)&&(W=ie.cells.find(le=>le.tmuxSession===e))}catch{}if(!$)return;F(Cf({session:e,cell:W||(s?{cell:s}:null)}));let X="";W?X=`${W.engine}${W.key?`·${W.key}`:""}`:A&&(X=A.attached?`attached · ${W_(A.activity)}`:A.activity?W_(A.activity):""),K(X)}I();const D=setInterval(I,4e3);return()=>{$=!1,clearInterval(D)}},[e,t,o]),c.jsxs("div",{className:"nc-app",children:[c.jsxs("header",{className:"nc-bar nc-bar-single",children:[c.jsxs("button",{onClick:u,title:m("sessions"),children:[c.jsx(Ze,{name:"chevronLeft",size:18}),c.jsx("span",{className:"nc-bar-label",children:m("sessions")})]}),c.jsxs("span",{className:"nc-bar-center",children:[c.jsx("b",{title:t?`${E} · ${t}`:E,children:E}),O&&c.jsx("small",{className:"nc-bar-sub",children:O})]}),c.jsxs("span",{className:"nc-bar-right",children:[c.jsx("button",{onClick:()=>Y(-1),title:m("zoom-out"),children:c.jsx(Ze,{name:"zoomOut",size:18})}),c.jsx("button",{onClick:()=>Y(1),title:m("zoom-in"),children:c.jsx(Ze,{name:"zoomIn",size:18})}),c.jsx("button",{onClick:()=>b($=>!$),title:m("composer"),children:c.jsx(Ze,{name:"keyboard",size:20})}),c.jsx("button",{onClick:()=>S($=>!$),title:m("files"),children:c.jsx(Ze,{name:"folder",size:20})})]})]}),c.jsx("div",{className:"nc-termwrap",children:c.jsx(mw,{session:e,node:t,token:o,readonly:a,takeSize:!0,sendRef:z,composerRef:Z,actionRef:G,ctrlRef:Q,setCtrlArmed:M,onFiles:k,fontSize:C,selectionMode:N,onSelectionModeChange:T,keyboardGesture:f.terminalKeyboardGesture})}),c.jsx(TE,{onKeyboard:()=>b($=>!$),onCellSwitcher:h,cellSwitcherOpen:p,send:$=>z.current($),action:$=>G.current($),ctrlArmed:B,onCtrl:L,selectionMode:N,onSelectionMode:T,keepKeyboardClosed:f.keybarKeepsKeyboardClosed,showEnter:f.showKeybarEnter,keybarLayout:f.keybarLayout}),y&&c.jsx(Cw,{submitText:$=>Z.current($),token:o,session:e,node:t,ownerId:n,keepKeyboardClosedOnVoice:f.voiceKeepsKeyboardClosed}),v&&c.jsx(gw,{session:e,node:t,token:o,filesEvent:w,onClose:()=>S(!1)})]})}function aR(){kn();const[e]=j.useState(sR),[t,n]=j.useState(e.token),[s,o]=j.useState(e.pair||""),a=j.useCallback(()=>{o("");try{sessionStorage.removeItem("nc_pair")}catch{}},[]),[u,h]=j.useState(!1),p=oR(),[f]=j.useState(()=>lN(typeof location<"u"?location.pathname:"/")),[v,S]=j.useState(f.id),y=v===Lr(null,Un),[b,w]=j.useState(null),k=ue=>{const je=ys(ue);w(je?{...je,...typeof(ue==null?void 0:ue.cellName)=="string"&&ue.cellName?{cellName:ue.cellName}:{}}:null)},[C,R]=j.useState([]),[E,F]=j.useState([]),[O,K]=j.useState([]),[Y,z]=j.useState(()=>f.ownerId?Pr():rR(f.name)),[Z,G]=j.useState(null),[Q,B]=j.useState(null),M=ue=>B(ys(ue)),[N,T]=j.useState(null),L=nf(t,p),$=j.useMemo(()=>(L||[]).filter(ue=>ue.instanceId).map(ue=>({instanceId:ue.instanceId,route:ue.route,label:ue.label,status:ue.status})),[L]),U=Vj(t,v,Y,z,$),x=U.decks;j.useEffect(()=>{const ue=new Map;for(const je of L||[]){const qe=(je.route||[je.name]).join("/"),pt=new Set((je.sessions||[]).map(_t=>_t.name));for(const _t of je.cells||[])!_t.cell||!_t.tmuxSession||_t.cell===_t.tmuxSession||pt.has(_t.cell)||ue.set(`${qe}:${_t.cell}`,`${qe}:${_t.tmuxSession}`)}z(je=>Ex(je,ue))},[L]),j.useEffect(()=>{U.localNodeId&&z(ue=>{const je=Oh(ue,U.localNodeId,$);return JSON.stringify(je)===JSON.stringify(ue)?ue:je})},[$,U.localNodeId]);const[I,D]=j.useState(null),[A,W]=j.useState(null),X=j.useRef(0),[ie,le]=j.useState(!1),[fe,we]=j.useState(nR),[pe,_e]=j.useState(!y),[He,ht]=j.useState(()=>y?localStorage.getItem(H_)==="1":!0),[Ye,Ae]=j.useState(!1),[Ne,ut]=j.useState("nodes"),[Pe,Ut]=j.useState(!1),[Rt,Dt]=j.useState(""),[vt,ft]=j.useState(!1),dt=(ue="nodes",je=!1,qe="")=>{ut(ue),Ut(je),Dt(qe),Ae(!0)},[re,xe]=j.useState(!1),[Oe,Fe]=j.useState({deviceDefault:"",localNodeId:"",localNameDefault:""}),[de,ze]=j.useState(!1);j.useEffect(()=>{try{localStorage.setItem(_b,String(fe))}catch{}},[fe]),j.useEffect(()=>{try{localStorage.setItem(H_,He?"1":"")}catch{}},[He]),j.useEffect(()=>{if(!t)return;let ue=!1;return Promise.all([ey(t),Ht("/api/config",t).then(je=>je.json())]).then(([je,qe])=>{ue||(Fe({deviceDefault:je.deviceName||"",localNodeId:je.nodeId||"",localNameDefault:je.localName||""}),ze(!!qe.readonlyDefault),(je.firstRun===!0&&!qe.readonlyDefault||s)&&xe(!0))}).catch(()=>{}),()=>{ue=!0}},[t,s]);const q=j.useCallback(async()=>{try{const je=await(await Ht("/api/sessions",t)).json();je.error||R(je.sessions||[])}catch{}try{const ue=await ws(t);F(ue.available?ue.cells||[]:[]),K(ue.available?ue.capabilities||[]:[])}catch{F([]),K([])}},[t]);j.useEffect(()=>{if(!p)return;q();const ue=setInterval(q,4e3);return()=>clearInterval(ue)},[p,q]),j.useEffect(()=>{let ue=!1;return Ht("/api/config",t).then(je=>je.json()).then(je=>{ue||Xj(je.version,je.uiVersion,"0.8.52-rc.15")}).catch(()=>{}),()=>{ue=!0}},[t]);const te=new Set([...C.map(ue=>ue.name),...L.flatMap(ue=>ue.sessions.map(je=>je.key))]),H=ki(Y),ae=ue=>z(je=>{const qe=Pw(ue,U.localNodeId,$)||ue,pt=_y(je,qe);return pt===je&&ki(je).length>=9&&U.setError(m("grid-full")),pt}),ce=async(ue,je=[])=>{try{await Xh(t,ue,je)}catch{return}const qe=je.length?`${je.join("/")}:${ue}`:ue;z(pt=>Hl(pt,qe)),q()},me=async(ue,je,qe=[])=>{try{await Z_(t,ue,je,qe)}catch{return}q()},Te=async(ue,je,qe=[])=>{await X_(t,{cell:ue,enabled:!!je},qe),q()},ve=async ue=>{if(!I)return;const{cell:je}=I,qe=Array.isArray(I.route)?I.route:[];if(ue.action==="up"){const _t=await Yh(t,{cell:je,boot:!!ue.boot,...ue.engine?{engine:ue.engine}:{},...ue.model!==void 0?{model:ue.model}:{},...ue.permissionPolicy?{permissionPolicy:ue.permissionPolicy}:{}},qe),bs=uf(_t);bs&&U.setError(bs.text)}else await Il(t,{cell:je,boot:!!ue.boot},qe);const pt=ue.action==="up"?!!ue.boot:ue.boot?!1:!!I.boot;W({id:++X.current,cell:je,route:qe,enabled:pt}),q()},Ce=j.useCallback(ue=>{W(je=>(je==null?void 0:je.id)===ue?null:je)},[]),nt=async ue=>{if(!(!(ue!=null&&ue.direct)||ie)){le(!0);try{await ef(t,ue.name,ue.tunnelStatus==="up"?"down":"up")}finally{le(!1)}}},ot=async(ue,je)=>{const qe=String(je||"").trim();return!(ue!=null&&ue.direct)||!Mr(qe)?!1:(await iy(t,ue.name,qe),!0)},Je=ue=>{const je=x.find(qe=>qe.id===ue);if(!je)return!1;try{const qe=window.open(fd(je,t),"_blank");return qe&&(qe.opener=null),!!qe}catch{return!1}},Mt=async ue=>{if(!ue||ue===v)return;const je=await U.select(ue),qe=U.records.find(pt=>pt.id===ue);S(ue),z(je),G(null),B(null);try{history.replaceState(null,"",fd(qe||ue,null))}catch{}},lt=async(ue,je)=>{const qe=await U.add(ue,je);await Mt(qe.id)},et=async(ue,je)=>{const qe=await U.rename(ue,je);if(ue===v){S(qe.id),z(Oh(qe.layout,U.localNodeId,$)),G(null),B(null);try{history.replaceState(null,"",fd(qe,null))}catch{}}},Zt=async ue=>{await U.remove(ue),ue===v&&await Mt(U.localMainId)},ni=async(ue,je)=>{!je||je===v||(await U.addTileTo(je,ue),z(qe=>Hl(qe,ue)))};if(!t)return c.jsxs("div",{className:"nc-auth",children:[c.jsx("p",{children:m("auth-prompt")}),c.jsx("input",{onChange:ue=>n(ue.target.value.trim()),placeholder:"token"}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:u,onChange:ue=>h(ue.target.checked)})," ",m("remember-device")]}),c.jsx("button",{onClick:()=>{(u?localStorage:sessionStorage).setItem("nc_token",t)},children:"ok"})]});const Wn=c.jsxs(c.Fragment,{children:[Ye&&c.jsx(Rj,{token:t,initialTab:Ne,initialLocation:Rt,startNewCell:Pe,onClose:()=>{Ae(!1),Ut(!1),Dt("")}}),re&&c.jsx(Pj,{token:t,initialPair:s,...Oe,onPairDone:a,onDone:()=>xe(!1)}),c.jsx(Ij,{token:t})]});if(!p)return N?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-single-overlay",children:c.jsx(F_,{peer:N,token:t,onBack:()=>T(null)})}),Wn]}):b?c.jsxs(c.Fragment,{children:[c.jsx(K_,{session:b.session,node:b.node,ownerId:b.ownerId,cellName:b.cellName,token:t,readonly:de,onBack:()=>w(null),onCellSwitcher:()=>ft(!0),cellSwitcherOpen:vt}),vt&&c.jsx(Hj,{token:t,current:b,onPick:ue=>{k(ue),ft(!1)},onClose:()=>ft(!1)}),Wn]}):c.jsxs(c.Fragment,{children:[c.jsx(Wx,{onPick:k,token:t,onSettings:dt,onOpenVlSession:T}),Wn]});const ui=y||!pe;return c.jsxs("div",{className:"nc-workspace",children:[ui&&c.jsx(XE,{sessions:C,cells:E,activeSessions:H,nodeGroups:L,fleetCapabilities:O,bootSettlement:A,onBootSettlementApplied:Ce,localNodeId:U.localNodeId,onPick:M,onAddTile:ae,onPower:D,onBoot:Te,onBootError:ue=>U.setError(String((ue==null?void 0:ue.message)||ue)),onNodePower:nt,onNodeRename:ot,onKill:ce,onVisibility:me,onNew:()=>dt("fleet",!0),onSettings:dt,onOpenVlSession:T,width:fe,collapsed:He,onResize:we,onToggleCollapse:()=>ht(ue=>!ue)}),c.jsxs("div",{className:"nc-workspace-main",children:[c.jsx(LN,{decks:x,currentDeck:v,onCreate:lt,onRename:et,onDelete:Zt,onReorder:U.reorder,onOpenWindow:Je,onNavigate:Mt,saveState:U.saveState,error:U.error,sidebarVisible:ui,onToggleSidebar:y?null:()=>_e(ue=>!ue)}),c.jsx(iN,{layout:Y,onLayoutChange:z,token:t,readonly:de,sessionsAlive:te,focusSession:Z,onFocus:G,onOpenSingle:M,decks:x,currentDeck:v,onSendToDeck:ni,cells:E,nodeGroups:L})]}),N&&c.jsx("div",{className:"nc-single-overlay",children:c.jsx(F_,{peer:N,token:t,onBack:()=>T(null)})}),Q&&c.jsx("div",{className:"nc-single-overlay",children:c.jsx(K_,{session:Q.session,node:Q.node,ownerId:Q.ownerId,cellName:Cf({session:Q.session,node:Q.node,ownerId:Q.ownerId,cells:E,nodeGroups:L}),token:t,readonly:de,onBack:()=>B(null)})}),I&&c.jsx(Bl,{cell:I,token:t,route:Array.isArray(I.route)?I.route:[],onConfirm:ve,onClose:()=>D(null)}),Wn]})}function lR(){kn();const e=j.useSyncExternalStore(Gj,$_,$_);if(!e.needed)return null;const t=e.kind==="install"?m("install-version-mismatch").replace("{v}",e.version):m("update-available").replace("{v}",e.version).replace(" "," ").trim();return c.jsxs("div",{className:"nc-update",role:"status","aria-live":"polite",children:[c.jsx("span",{className:"nc-update-msg",children:t}),e.kind==="reload"&&c.jsx("button",{className:"nc-update-btn",onClick:Zj,children:m("reload")})]})}Jj();h0.createRoot(document.getElementById("root")).render(c.jsxs(r0.StrictMode,{children:[c.jsx(aR,{}),c.jsx(lR,{})]}));
|
package/frontend/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-title" content="NexusCrew" />
|
|
12
12
|
<link rel="manifest" href="/manifest.json" />
|
|
13
13
|
<title>NexusCrew</title>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-CB3o67BZ.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-_kotgT8Z.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
package/frontend/dist/sw.js
CHANGED
|
@@ -2,6 +2,17 @@ self.addEventListener('install', () => self.skipWaiting());
|
|
|
2
2
|
self.addEventListener('activate', () => self.clients.claim());
|
|
3
3
|
self.addEventListener('fetch', (e) => e.respondWith(fetch(e.request)));
|
|
4
4
|
|
|
5
|
+
// applyUpdate() (lib/sw-update.js) manda questo messaggio a un worker in
|
|
6
|
+
// waiting e aspetta il `controllerchange`. Senza un listener il messaggio
|
|
7
|
+
// cadeva nel vuoto: scattava il reload di fallback, il worker restava in
|
|
8
|
+
// waiting, e al ricaricamento `reg.waiting` faceva ricomparire il banner —
|
|
9
|
+
// "nuova versione disponibile" per sempre, e il bottone non poteva spegnerlo.
|
|
10
|
+
// Un worker installato da una versione precedente di questo file resta in
|
|
11
|
+
// waiting fino a che non lo si attiva: e' quello il caso che si incastrava.
|
|
12
|
+
self.addEventListener('message', (e) => {
|
|
13
|
+
if (e && e.data && e.data.type === 'SKIP_WAITING') self.skipWaiting();
|
|
14
|
+
});
|
|
15
|
+
|
|
5
16
|
// Web Push del MCP bridge: payload JSON {title, body?, lang?, url?} dal server.
|
|
6
17
|
// tag fisso 'nexuscrew': le notifiche si sostituiscono invece di accumularsi.
|
|
7
18
|
self.addEventListener('push', (e) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.8.52-rc.
|
|
1
|
+
{"version":"0.8.52-rc.15"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// lib/cells/scope-guard.js — il punto in cui lo scope celle diventa effettivo.
|
|
3
|
+
//
|
|
4
|
+
// Sta in testa al router /api perche' i canali da cui una cella puo' trapelare
|
|
5
|
+
// sono tredici, e un permesso che vale su dodici di essi non e' un permesso:
|
|
6
|
+
// e' un'etichetta. Gli elenchi (cosa esce) e le azioni (cosa si puo' toccare)
|
|
7
|
+
// passano dallo stesso predicato di lib/cells/scope.js.
|
|
8
|
+
//
|
|
9
|
+
// Due scelte deliberate:
|
|
10
|
+
//
|
|
11
|
+
// * il bersaglio di ogni route e' DICHIARATO in una tabella, non indovinato.
|
|
12
|
+
// Una regola magica sbaglia in silenzio quando qualcuno aggiunge una route;
|
|
13
|
+
// un elenco esplicito, quando qualcuno lo dimentica, fa scattare la rete di
|
|
14
|
+
// sicurezza qui sotto.
|
|
15
|
+
// * fail-closed sulle route sconosciute che nominano una cella: se un corpo
|
|
16
|
+
// porta `cell`, `to.cell` o `session` e la route non e' dichiarata, si nega.
|
|
17
|
+
// E' l'unico modo perche' una route aggiunta domani non nasca scoperta.
|
|
18
|
+
const { createCellScope } = require('./scope.js');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Chiavi da cui, in una RISPOSTA, esce un elenco di celle o di sessioni.
|
|
22
|
+
// Coprono /cells, /fleet/status, /fleet/definitions, /sessions.
|
|
23
|
+
const CELL_LIST_KEYS = ['cells'];
|
|
24
|
+
const SESSION_LIST_KEYS = ['sessions', 'unmanaged'];
|
|
25
|
+
|
|
26
|
+
// Route che AGISCONO su una cella o su una sessione, con il punto esatto in cui
|
|
27
|
+
// leggere il bersaglio. `null` = la route e' nota e non ha un bersaglio cella.
|
|
28
|
+
function declaredTarget(req) {
|
|
29
|
+
const p = req.path || '';
|
|
30
|
+
const b = (req.body && typeof req.body === 'object') ? req.body : {};
|
|
31
|
+
const q = req.query || {};
|
|
32
|
+
|
|
33
|
+
// Azioni sul ciclo di vita di una cella: il bersaglio e' `body.cell`.
|
|
34
|
+
if (/^\/fleet\/(up|down|restart|engine|boot)$/.test(p)) return { cell: b.cell };
|
|
35
|
+
// Definire/modificare/rimuovere celle e' una capacita' a se': chi ha uno
|
|
36
|
+
// scope ristretto non deve poter creare la cella che gli manca.
|
|
37
|
+
if (/^\/fleet\/(define-cell|edit-cell|remove-cell|restore-cells|import-cell)$/.test(p)) return { deny: 'scope-cannot-define-cells' };
|
|
38
|
+
// Messaggio a una cella: il bersaglio e' dichiarato due volte, e devono
|
|
39
|
+
// valere entrambe (il codice a valle le confronta gia' fra loro).
|
|
40
|
+
if (p === '/cells/send') return { cell: b.to && b.to.cell, session: b.to && b.to.tmuxSession };
|
|
41
|
+
// Sessioni: creazione, eliminazione, visibilita', file.
|
|
42
|
+
if (p === '/sessions' && req.method === 'POST') return { session: b.name };
|
|
43
|
+
if (/^\/sessions\/[^/]+/.test(p)) return { session: decodeURIComponent(p.split('/')[2] || '') };
|
|
44
|
+
if (p === '/files' || p.startsWith('/files/')) return { session: q.session || b.session };
|
|
45
|
+
return undefined; // route non dichiarata
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Rete di sicurezza: un corpo che nomina una cella o una sessione su una route
|
|
49
|
+
// non dichiarata. Meglio un 403 su qualcosa di legittimo — che si nota subito e
|
|
50
|
+
// si aggiunge alla tabella — di un canale aperto che nessuno vede.
|
|
51
|
+
function undeclaredTarget(req) {
|
|
52
|
+
const b = (req.body && typeof req.body === 'object') ? req.body : {};
|
|
53
|
+
const q = req.query || {};
|
|
54
|
+
const cell = b.cell || (b.to && b.to.cell);
|
|
55
|
+
const session = b.session || b.tmuxSession || (b.to && b.to.tmuxSession) || q.session;
|
|
56
|
+
if (cell || session) return { cell, session };
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function filterPayload(payload, scope) {
|
|
61
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload;
|
|
62
|
+
const out = { ...payload };
|
|
63
|
+
for (const key of CELL_LIST_KEYS) {
|
|
64
|
+
if (Array.isArray(out[key])) out[key] = scope.filterCells(out[key]);
|
|
65
|
+
}
|
|
66
|
+
for (const key of SESSION_LIST_KEYS) {
|
|
67
|
+
if (Array.isArray(out[key])) out[key] = scope.filterSessions(out[key]);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parser JSON usato SOLO sulle richieste federate. Non si puo' montare un
|
|
73
|
+
// parser globale: le route locali catturano `rawBody` con `verify` per la firma
|
|
74
|
+
// del bridge, e `verify` non viene chiamato se il corpo e' gia' stato
|
|
75
|
+
// consumato; inoltre i limiti di dimensione per-route (16kb per notify, 8kb per
|
|
76
|
+
// i comandi VL) verrebbero saltati, perche' express.json esce subito quando
|
|
77
|
+
// `req.body` esiste gia'. Le richieste locali devono arrivare INTATTE ai loro
|
|
78
|
+
// parser.
|
|
79
|
+
function federatedBodyParser(limit) {
|
|
80
|
+
const parse = require('express').json({ limit });
|
|
81
|
+
return (req, res, next) => {
|
|
82
|
+
if (req.body !== undefined) return next();
|
|
83
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
84
|
+
if (method === 'GET' || method === 'HEAD' || method === 'DELETE') return next();
|
|
85
|
+
return parse(req, res, () => next());
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createCellScopeGuard({
|
|
90
|
+
nodesPath, loadStoreImpl, cellForSession,
|
|
91
|
+
// resolveOrigin(req) -> {ok, trust, visited}. In produzione e' l'origin
|
|
92
|
+
// resolver basato sulla prova di hop; nei test e' un seam, cosi' questo
|
|
93
|
+
// modulo prova il GUARD e non di nuovo la firma (che ha i suoi test).
|
|
94
|
+
resolveOrigin,
|
|
95
|
+
bodyLimit = '1mb',
|
|
96
|
+
} = {}) {
|
|
97
|
+
const scoper = createCellScope({ nodesPath, loadStoreImpl, cellForSession });
|
|
98
|
+
const parseFederatedBody = federatedBodyParser(bodyLimit);
|
|
99
|
+
|
|
100
|
+
return async function cellScopeGuard(req, res, next) {
|
|
101
|
+
let origin;
|
|
102
|
+
try {
|
|
103
|
+
origin = await resolveOrigin(req);
|
|
104
|
+
} catch (_) {
|
|
105
|
+
origin = null;
|
|
106
|
+
}
|
|
107
|
+
// Un hop che non verifica NON e' una richiesta federata, e questo guard non
|
|
108
|
+
// e' il posto dove deciderne l'esito: le route che sanno rifiutarlo lo
|
|
109
|
+
// fanno gia' con il loro codice (401 su audio, non 403), e sostituirlo qui
|
|
110
|
+
// cambierebbe una risposta di sicurezza esistente senza migliorarla.
|
|
111
|
+
// Non e' nemmeno una scappatoia: `cleanHeaders` cancella gli header di hop
|
|
112
|
+
// in ingresso e il proxy li riscrive lui, quindi un peer non puo' ne'
|
|
113
|
+
// toglierli ne' falsificarli. Chi puo' inventarne uno possiede gia' il
|
|
114
|
+
// Bearer locale — e' il proprietario della macchina, che non si limita da
|
|
115
|
+
// solo.
|
|
116
|
+
if (!origin || origin.ok !== true) return next();
|
|
117
|
+
if (origin.trust !== 'federated') return next();
|
|
118
|
+
|
|
119
|
+
const scope = scoper.resolve({ trust: 'federated', visited: origin.visited });
|
|
120
|
+
req.cellScope = scope;
|
|
121
|
+
if (scope.mode === 'all') return next();
|
|
122
|
+
|
|
123
|
+
// Il bersaglio di molte azioni sta nel corpo: qui, e solo per le federate,
|
|
124
|
+
// va parsato prima di poterlo guardare.
|
|
125
|
+
return parseFederatedBody(req, res, () => applyScope(req, res, next, scope));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function applyScope(req, res, next, scope) {
|
|
129
|
+
// --- azioni ---------------------------------------------------------
|
|
130
|
+
const declared = declaredTarget(req);
|
|
131
|
+
if (declared === undefined) {
|
|
132
|
+
const sneaky = undeclaredTarget(req);
|
|
133
|
+
if (sneaky) {
|
|
134
|
+
return res.status(403).json({ error: 'route non dichiarata per lo scope celle', reason: 'undeclared-cell-route' });
|
|
135
|
+
}
|
|
136
|
+
} else if (declared) {
|
|
137
|
+
if (declared.deny) return res.status(403).json({ error: 'fuori dallo scope celle', reason: declared.deny });
|
|
138
|
+
if (declared.cell !== undefined && !scope.allowsCell(declared.cell)) {
|
|
139
|
+
return res.status(403).json({ error: 'fuori dallo scope celle', reason: 'cell-not-granted' });
|
|
140
|
+
}
|
|
141
|
+
if (declared.session !== undefined && !scope.allowsSession(declared.session)) {
|
|
142
|
+
return res.status(403).json({ error: 'fuori dallo scope celle', reason: 'session-not-granted' });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- letture --------------------------------------------------------
|
|
147
|
+
// Si avvolge res.json invece di riscrivere ogni handler: gli elenchi sono
|
|
148
|
+
// quattro e crescono, e un handler nuovo nascerebbe scoperto.
|
|
149
|
+
const json = res.json.bind(res);
|
|
150
|
+
res.json = (payload) => json(filterPayload(payload, scope));
|
|
151
|
+
return next();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { createCellScopeGuard, declaredTarget, filterPayload };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// lib/cells/scope.js — quali celle di QUESTO nodo un peer federato puo' vedere.
|
|
3
|
+
//
|
|
4
|
+
// Gemello di lib/audio/acl.js, e per le stesse ragioni:
|
|
5
|
+
// * la sorgente della decisione e' il node store locale, mai il corpo della
|
|
6
|
+
// richiesta. Un peer non dichiara il proprio scope: lo subisce.
|
|
7
|
+
// * l'identita' arriva dalla catena `visited` che costruisce il server
|
|
8
|
+
// (controlledVisited), non da un campo dichiarato.
|
|
9
|
+
//
|
|
10
|
+
// Due invarianti che una versione semplificata romperebbe in silenzio:
|
|
11
|
+
//
|
|
12
|
+
// 1. In multi-hop lo scope e' l'INTERSEZIONE fra chi consegna e chi origina.
|
|
13
|
+
// `canTransit` autorizza il transito, non l'accesso: un peer B puo'
|
|
14
|
+
// instradare una richiesta di C. Guardare solo B lascerebbe C ereditare i
|
|
15
|
+
// permessi di B. lib/audio/acl.js risolve lo stesso caso controllando
|
|
16
|
+
// entrambi, e qui va replicato invece che semplificato.
|
|
17
|
+
// 2. Una sessione tmux e' concessa solo se lo e' la CELLA a cui appartiene, e
|
|
18
|
+
// una sessione che non appartiene a nessuna cella non e' "libera": e'
|
|
19
|
+
// fuori scope. Altrimenti basterebbe una tmux creata a mano per aggirare
|
|
20
|
+
// il permesso — e `/ws` attacca proprio per nome di sessione.
|
|
21
|
+
//
|
|
22
|
+
// Il permesso e' indicizzato per `nodeId`, non per `name`: il name e' uno slug
|
|
23
|
+
// locale rinominabile, il nodeId e' l'identita' stabile provata dal pairing.
|
|
24
|
+
const nodesStore = require('../nodes/store.js');
|
|
25
|
+
|
|
26
|
+
// Scope di un singolo nodo, normalizzato. `all` non elenca nulla di proposito:
|
|
27
|
+
// una lista che significa "tutte" invecchierebbe a ogni cella nuova.
|
|
28
|
+
function scopeOf(node) {
|
|
29
|
+
if (!node) return { mode: 'none', cells: new Set() };
|
|
30
|
+
const mode = node.cellVisibility || 'all';
|
|
31
|
+
if (mode === 'all') return { mode: 'all', cells: null };
|
|
32
|
+
if (mode === 'none') return { mode: 'none', cells: new Set() };
|
|
33
|
+
return { mode: 'selected', cells: new Set(Array.isArray(node.cells) ? node.cells : []) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Intersezione di due scope. `all` e' l'elemento neutro; `none` assorbe.
|
|
37
|
+
function intersect(a, b) {
|
|
38
|
+
if (a.mode === 'none' || b.mode === 'none') return { mode: 'none', cells: new Set() };
|
|
39
|
+
if (a.mode === 'all') return b;
|
|
40
|
+
if (b.mode === 'all') return a;
|
|
41
|
+
return { mode: 'selected', cells: new Set([...a.cells].filter((c) => b.cells.has(c))) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findByNodeId(st, nodeId) {
|
|
45
|
+
if (!st || !Array.isArray(st.nodes) || !nodeId) return null;
|
|
46
|
+
return st.nodes.find((n) => n && n.nodeId === nodeId) || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// createCellScope(): deps esplicite, nessun accesso globale.
|
|
50
|
+
// nodesPath percorso del node store
|
|
51
|
+
// loadStoreImpl iniettabile nei test
|
|
52
|
+
// cellForSession mappa tmuxSession -> id cella (null se non e' di una cella)
|
|
53
|
+
function createCellScope({ nodesPath, loadStoreImpl = nodesStore.loadStore, cellForSession = () => null } = {}) {
|
|
54
|
+
// Scope "tutto", usato dal percorso locale: il proprietario della macchina
|
|
55
|
+
// non si limita da solo, e questo modulo non e' il posto dove decidere
|
|
56
|
+
// altrimenti.
|
|
57
|
+
function openScope() {
|
|
58
|
+
return buildApi({ mode: 'all', cells: null });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildApi(resolved) {
|
|
62
|
+
const allowsCell = (cell) => {
|
|
63
|
+
if (resolved.mode === 'all') return true;
|
|
64
|
+
if (resolved.mode === 'none') return false;
|
|
65
|
+
return typeof cell === 'string' && resolved.cells.has(cell);
|
|
66
|
+
};
|
|
67
|
+
const allowsSession = (session) => {
|
|
68
|
+
if (resolved.mode === 'all') return true;
|
|
69
|
+
const cell = cellForSession(session);
|
|
70
|
+
// Nessuna cella dietro la sessione => fuori scope. Vedi invariante 2.
|
|
71
|
+
if (!cell) return false;
|
|
72
|
+
return allowsCell(cell);
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
mode: resolved.mode,
|
|
76
|
+
cells: resolved.cells ? [...resolved.cells] : null,
|
|
77
|
+
allowsCell,
|
|
78
|
+
allowsSession,
|
|
79
|
+
// Filtri: gli elenchi si restringono con lo STESSO predicato che decide
|
|
80
|
+
// le azioni. Due implementazioni divergenti sarebbero un bug latente.
|
|
81
|
+
filterCells: (list) => (Array.isArray(list) ? list.filter((c) => allowsCell(c && (c.cell ?? c.id))) : []),
|
|
82
|
+
filterSessions: (list) => (Array.isArray(list) ? list.filter((s) => allowsSession(s && (s.name ?? s.tmuxSession))) : []),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// resolve({trust, visited}) -> api dello scope.
|
|
87
|
+
// trust 'local-bridge' (o assente e non federato) : nessuna restrizione
|
|
88
|
+
// trust 'federated' : intersezione consegnante ∩ origine
|
|
89
|
+
function resolve({ trust, visited } = {}) {
|
|
90
|
+
if (trust !== 'federated') return openScope();
|
|
91
|
+
const chain = Array.isArray(visited) ? visited : [];
|
|
92
|
+
// Serve almeno origine + questo nodo. Una catena piu' corta non identifica
|
|
93
|
+
// nessun peer: fail-closed.
|
|
94
|
+
if (chain.length < 2) return buildApi({ mode: 'none', cells: new Set() });
|
|
95
|
+
|
|
96
|
+
let st;
|
|
97
|
+
try { st = loadStoreImpl(nodesPath); } catch (_) { st = null; }
|
|
98
|
+
if (!st) return buildApi({ mode: 'none', cells: new Set() });
|
|
99
|
+
|
|
100
|
+
const deliveringId = chain[chain.length - 2];
|
|
101
|
+
const originId = chain[0];
|
|
102
|
+
const delivering = scopeOf(findByNodeId(st, deliveringId));
|
|
103
|
+
// Quando origine e consegnante coincidono (hop singolo) l'intersezione con
|
|
104
|
+
// se stesso e' identita': nessun caso speciale da mantenere allineato.
|
|
105
|
+
const origin = originId === deliveringId ? delivering : scopeOf(findByNodeId(st, originId));
|
|
106
|
+
return buildApi(intersect(delivering, origin));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { resolve };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { createCellScope, scopeOf, intersect };
|
package/lib/mcp/tools.js
CHANGED
|
@@ -273,7 +273,11 @@ const TOOLS = [
|
|
|
273
273
|
// Con un target remoto l'esito e' quello del nodo che ha consegnato:
|
|
274
274
|
// `delivered` da solo direbbe "0 tentativi qui", che e' vero e inutile.
|
|
275
275
|
// Restituirlo cosi' evita di far leggere un successo dove c'e' un rifiuto.
|
|
276
|
-
|
|
276
|
+
// Nessun conteggio: il dispatcher non lo propaga, e un ramo che non puo'
|
|
277
|
+
// mai essere vero e' codice morto travestito da informazione
|
|
278
|
+
// (rilievo R1 di DevAuditor su rc.14). Se un giorno servira' il dettaglio,
|
|
279
|
+
// va fatto propagare da forward(), non dedotto qui.
|
|
280
|
+
if (target) return { status: j.status, ...(j.reason ? { reason: j.reason } : {}) };
|
|
277
281
|
return { delivered: j.delivered };
|
|
278
282
|
},
|
|
279
283
|
},
|
package/lib/nodes/store.js
CHANGED
|
@@ -108,8 +108,17 @@ const NODE_KEYS = new Set([
|
|
|
108
108
|
'name', 'ssh', 'sshPort', 'remotePort', 'localPort', 'keyPath', 'identityFile',
|
|
109
109
|
'roles', 'rolesKnown', 'token', 'acceptToken', 'nodeId', 'transport', 'autostart', 'visibility', 'selected',
|
|
110
110
|
'direction', 'reversePort', 'shared', 'label', 'reversePool',
|
|
111
|
+
// Quali CELLE di questo nodo il peer puo' vedere. Distinto da `visibility`,
|
|
112
|
+
// che governa il TRANSITO (attraverso chi passa il traffico) e non l'accesso.
|
|
113
|
+
'cellVisibility', 'cells',
|
|
111
114
|
]);
|
|
112
115
|
|
|
116
|
+
// Nome di cella: stessa forma usata da fleet/definitions.js, cells/routes.js e
|
|
117
|
+
// mcp/cells.js. E' `cell` la chiave del permesso — unica nel nodo e immutabile —
|
|
118
|
+
// non `tmuxSession`, che e' derivata e ammette override non canonici.
|
|
119
|
+
const CELL_ID_RE = /^[A-Za-z0-9._-]{1,32}$/;
|
|
120
|
+
const MAX_CELLS = 128;
|
|
121
|
+
|
|
113
122
|
const REVERSE_POOL_SLOT_STATES = new Set(['active', 'ready', 'reserved', 'draining', 'quarantined', 'retired']);
|
|
114
123
|
const REVERSE_POOL_VERIFICATIONS = new Set(['verified', 'unverifiable', 'missing', 'invalidated']);
|
|
115
124
|
const REVERSE_POOL_PHASES = new Set(['active', 'prepared', 'switched', 'abandoned']);
|
|
@@ -265,6 +274,28 @@ function parseNode(n, schemaVersion = SCHEMA_VERSION) {
|
|
|
265
274
|
if (selected.some((id) => typeof id !== 'string' || !NODE_ID_RE.test(id))) return null;
|
|
266
275
|
out.selected = selected;
|
|
267
276
|
}
|
|
277
|
+
// Scope celle. Il default e' `all` quando il campo e' ASSENTE: un default
|
|
278
|
+
// fail-closed renderebbe muta l'intera flotta al primo aggiornamento, senza
|
|
279
|
+
// che nessuno abbia deciso nulla. Restringere deve restare un atto esplicito.
|
|
280
|
+
// `selected` con lista vuota significa invece "nessuna cella", ed e' proprio
|
|
281
|
+
// per distinguerlo da "campo assente" che la modalita' e' un campo a parte.
|
|
282
|
+
if (n.cellVisibility !== undefined) {
|
|
283
|
+
if (!['all', 'none', 'selected'].includes(n.cellVisibility)) return null;
|
|
284
|
+
out.cellVisibility = n.cellVisibility;
|
|
285
|
+
} else {
|
|
286
|
+
out.cellVisibility = 'all';
|
|
287
|
+
}
|
|
288
|
+
if (n.cells !== undefined) {
|
|
289
|
+
// Elencare celle senza dichiarare la modalita' e' ambiguo: l'elenco
|
|
290
|
+
// sembrerebbe un permesso mentre non lo e'. Meglio rifiutare che indovinare.
|
|
291
|
+
if (out.cellVisibility !== 'selected') return null;
|
|
292
|
+
if (!Array.isArray(n.cells) || n.cells.length > MAX_CELLS) return null;
|
|
293
|
+
const cells = [...new Set(n.cells)];
|
|
294
|
+
if (cells.some((id) => typeof id !== 'string' || !CELL_ID_RE.test(id))) return null;
|
|
295
|
+
out.cells = cells;
|
|
296
|
+
} else if (out.cellVisibility === 'selected') {
|
|
297
|
+
out.cells = [];
|
|
298
|
+
}
|
|
268
299
|
// Campo opzionale per compatibilita' con gli store 0.8.0: se assente, ssh
|
|
269
300
|
// continua a usare la porta risolta da ~/.ssh/config o il default OpenSSH.
|
|
270
301
|
if (n.sshPort !== undefined) out.sshPort = n.sshPort;
|
|
@@ -577,6 +608,10 @@ function redactNode(n) {
|
|
|
577
608
|
};
|
|
578
609
|
if (n.identityFile || n.keyPath) out.hasIdentity = true;
|
|
579
610
|
if (n.visibility === 'selected') out.selected = [...(n.selected || [])];
|
|
611
|
+
// Lo scope celle non e' un segreto: e' una decisione dell'operatore e va
|
|
612
|
+
// mostrata, come `selected`. La Settings UI deve poterla leggere per dirla.
|
|
613
|
+
out.cellVisibility = n.cellVisibility || 'all';
|
|
614
|
+
if (out.cellVisibility === 'selected') out.cells = [...(n.cells || [])];
|
|
580
615
|
if (n.sshPort !== undefined) out.sshPort = n.sshPort;
|
|
581
616
|
if (n.nodeId) out.nodeId = n.nodeId;
|
|
582
617
|
if (n.reversePool) {
|
package/lib/proxy/federation.js
CHANGED
|
@@ -1084,7 +1084,7 @@ function localRouter({ nodesPath, localPort, localCredential, readonly, hopSecre
|
|
|
1084
1084
|
return r;
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
-
function forwardUpgrade({ req, socket, head, nodesPath, localPort, localCredential, ingress, readonly = () => false, activeSockets = null }) {
|
|
1087
|
+
function forwardUpgrade({ req, socket, head, nodesPath, localPort, localCredential, ingress, readonly = () => false, activeSockets = null, hopSecret = null }) {
|
|
1088
1088
|
if (readonly()) return reject(socket, 403);
|
|
1089
1089
|
const parsed = parseRoute(req.url.replace(/^\/(?:api\/route|federation\/route)/, ''));
|
|
1090
1090
|
if (!parsed || parsed.resource !== '/ws') return reject(socket, 404);
|
|
@@ -1103,7 +1103,18 @@ function forwardUpgrade({ req, socket, head, nodesPath, localPort, localCredenti
|
|
|
1103
1103
|
}
|
|
1104
1104
|
const up = net.connect({ host: '127.0.0.1', port });
|
|
1105
1105
|
up.once('connect', () => {
|
|
1106
|
-
|
|
1106
|
+
// Ultimo hop: l'upgrade rientra nell'API locale col Bearer locale e da li'
|
|
1107
|
+
// in poi sarebbe indistinguibile da un attach diretto di chi possiede quel
|
|
1108
|
+
// token. La catena PIU' la prova di hop sono cio' che lo distingue —
|
|
1109
|
+
// esattamente come fa routeHandler per il percorso HTTP. Senza, il nodo che
|
|
1110
|
+
// possiede la sessione non sa quale peer stia aprendo il suo PTY, e ogni
|
|
1111
|
+
// permesso per-cella diventa decorativo: `/ws` attacca per NOME di
|
|
1112
|
+
// sessione, quindi basta indovinarlo.
|
|
1113
|
+
const secret = typeof hopSecret === 'function' ? hopSecret() : hopSecret;
|
|
1114
|
+
const hopProof = !parsed.route.length && secret
|
|
1115
|
+
? signHop(secret, { method: req.method || 'GET', path, visited })
|
|
1116
|
+
: null;
|
|
1117
|
+
const headers = cleanHeaders(req.headers, credential, visited, hopProof);
|
|
1107
1118
|
const lines = [`GET ${path} HTTP/1.1`, `Host: 127.0.0.1:${port}`];
|
|
1108
1119
|
for (const [k, v] of Object.entries(headers)) lines.push(`${k}: ${Array.isArray(v) ? v.join(', ') : v}`);
|
|
1109
1120
|
lines.push('Connection: Upgrade', 'Upgrade: websocket', '', '');
|
package/lib/server.js
CHANGED
|
@@ -39,6 +39,9 @@ const { isConsent: isAudioConsent } = require('./audio/consent.js');
|
|
|
39
39
|
const { createOriginResolver } = require('./audio/origin.js');
|
|
40
40
|
const { createAudioAcl } = require('./audio/acl.js');
|
|
41
41
|
const { createDispatcher } = require('./audio/dispatch.js');
|
|
42
|
+
const { createCellScopeGuard } = require('./cells/scope-guard.js');
|
|
43
|
+
const { createCellScope } = require('./cells/scope.js');
|
|
44
|
+
const { cellIdFromTmuxSession } = require('./fleet/definitions.js');
|
|
42
45
|
// Budget a tre finestre, riusato dalle notifiche federate: e' gia' scritto per
|
|
43
46
|
// limitare il danno di un nodo che mente sulla propria cella (il tetto per
|
|
44
47
|
// target si calcola sul NODO, che la catena visited verifica).
|
|
@@ -48,7 +51,8 @@ const { createReceiptStore } = require('./audio/receipt.js');
|
|
|
48
51
|
const audioAdapters = require('./audio/adapters.js');
|
|
49
52
|
const audioGroups = require('./audio/groups.js');
|
|
50
53
|
const { bridgeSecretPath, loadOrCreateBridgeSecret, createNonceCache } = require('./audio/bridge-auth.js');
|
|
51
|
-
const { createHopSecret } = require('./proxy/hop-proof.js');
|
|
54
|
+
const { createHopSecret, verifyHop, HOP_HEADER } = require('./proxy/hop-proof.js');
|
|
55
|
+
const { parseVisitedChain } = require('./audio/origin.js');
|
|
52
56
|
const { settingsRoutes, publicPeeringRoutes } = require('./settings/routes.js');
|
|
53
57
|
const decksStore = require('./decks/store.js');
|
|
54
58
|
const { decksRoutes } = require('./decks/routes.js');
|
|
@@ -109,6 +113,10 @@ function createServer(opts = {}) {
|
|
|
109
113
|
// alla porta sbagliata (bug reale: 401/"unauthorized" dal servizio su cfg.port).
|
|
110
114
|
let server = null;
|
|
111
115
|
const previews = createPreviewSampler(cfg.tmuxBin);
|
|
116
|
+
// Seam di test per l'esistenza di una sessione: senza, un test sullo scope
|
|
117
|
+
// dell'attach non discrimina, perche' ogni sessione risulta assente e il
|
|
118
|
+
// rifiuto arriverebbe comunque — un verde che non prova nulla.
|
|
119
|
+
const sessionExistsImpl = cfg.sessionExistsSeam || ((name) => sessionExists(cfg.tmuxBin, name));
|
|
112
120
|
// MCP bridge (notify/ask/push): lo stato vive accanto al token (dirname del
|
|
113
121
|
// tokenPath = ~/.nexuscrew di default) cosi' le istanze isolate via opts/env
|
|
114
122
|
// nei test NON scrivono mai nella home reale. Tutto lazy: vapid.json/asks.json
|
|
@@ -545,6 +553,51 @@ function createServer(opts = {}) {
|
|
|
545
553
|
// ma il token chiude anche altri processi locali della stessa macchina.
|
|
546
554
|
const api = express.Router();
|
|
547
555
|
api.use(requireToken(tokenStore));
|
|
556
|
+
// Origine per lo scope celle: stessa prova di hop usata da audio e notify.
|
|
557
|
+
// requireCell:false perche' qui interessa QUALE NODO parla, non quale cella
|
|
558
|
+
// dichiari — la cella e' solo attestata e non deve decidere un permesso.
|
|
559
|
+
const cellScopeOrigin = createOriginResolver({
|
|
560
|
+
localNodeId: () => (nodesStore.loadStore(nodesPath) || {}).nodeId || null,
|
|
561
|
+
hopSecret: () => hopSecret,
|
|
562
|
+
});
|
|
563
|
+
const cellScoper = createCellScope({
|
|
564
|
+
nodesPath,
|
|
565
|
+
cellForSession: (session) => cellIdFromTmuxSession(session),
|
|
566
|
+
});
|
|
567
|
+
// Variante SINCRONA per l'upgrade WebSocket: verifica la prova di hop e la
|
|
568
|
+
// catena senza passare dal resolver async. Un upgrade non federato — o con
|
|
569
|
+
// una prova che non regge — resta senza restrizioni, esattamente come una
|
|
570
|
+
// richiesta locale: la prova la costruisce il proxy, un peer non puo' ne'
|
|
571
|
+
// toglierla ne' falsificarla.
|
|
572
|
+
const wsCellScope = (req) => {
|
|
573
|
+
try {
|
|
574
|
+
const headers = (req && req.headers) || {};
|
|
575
|
+
const proof = headers[HOP_HEADER];
|
|
576
|
+
if (!proof) return cellScoper.resolve({ trust: 'local-bridge' });
|
|
577
|
+
const self = (nodesStore.loadStore(nodesPath) || {}).nodeId || null;
|
|
578
|
+
const visited = parseVisitedChain(headers['x-nexuscrew-visited'], self);
|
|
579
|
+
if (!visited) return cellScoper.resolve({ trust: 'federated', visited: [] });
|
|
580
|
+
const ok = verifyHop(hopSecret, {
|
|
581
|
+
method: req.method || 'GET', path: '/ws', visited,
|
|
582
|
+
}, proof);
|
|
583
|
+
if (!ok) return cellScoper.resolve({ trust: 'federated', visited: [] });
|
|
584
|
+
return cellScoper.resolve({ trust: 'federated', visited });
|
|
585
|
+
} catch (_) {
|
|
586
|
+
// Fail-closed: se non si riesce a stabilire l'origine, nessuna sessione.
|
|
587
|
+
return cellScoper.resolve({ trust: 'federated', visited: [] });
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
// Scope celle (NC-E): un punto solo decide cosa un peer federato vede e puo'
|
|
591
|
+
// toccare. Sta qui, prima di ogni route, perche' i canali da cui una cella
|
|
592
|
+
// trapela sono molti e crescono: filtrarli uno per uno significa dimenticarne
|
|
593
|
+
// uno, e un permesso che vale su quasi tutti i canali non e' un permesso.
|
|
594
|
+
// Le richieste locali non lo attraversano: il proprietario della macchina non
|
|
595
|
+
// si limita da solo.
|
|
596
|
+
api.use(createCellScopeGuard({
|
|
597
|
+
nodesPath,
|
|
598
|
+
cellForSession: (session) => cellIdFromTmuxSession(session),
|
|
599
|
+
resolveOrigin: (req) => cellScopeOrigin.resolve(req, { requireCell: false }),
|
|
600
|
+
}));
|
|
548
601
|
api.get('/sessions', async (_req, res) => {
|
|
549
602
|
try {
|
|
550
603
|
const sessions = await listSessions(cfg.tmuxBin);
|
|
@@ -901,13 +954,13 @@ function createServer(opts = {}) {
|
|
|
901
954
|
let u; try { u = new URL(req.url, 'http://127.0.0.1'); } catch (_) { return socket.destroy(); }
|
|
902
955
|
const given = bearerFrom(req) || u.searchParams.get('token') || '';
|
|
903
956
|
if (!verify(tokenHolder.value, given)) return socket.destroy();
|
|
904
|
-
federation.forwardUpgrade({ req, socket, head, nodesPath, localPort: runtimePort, localCredential: () => tokenHolder.value, ingress: null, readonly: proxyReadonly, activeSockets: proxySockets });
|
|
957
|
+
federation.forwardUpgrade({ req, socket, head, nodesPath, localPort: runtimePort, localCredential: () => tokenHolder.value, ingress: null, readonly: proxyReadonly, activeSockets: proxySockets, hopSecret: () => hopSecret });
|
|
905
958
|
return;
|
|
906
959
|
}
|
|
907
960
|
if (pathname.startsWith('/federation/route/')) {
|
|
908
961
|
const ingress = federation.peerFromToken(nodesPath, bearerFrom(req));
|
|
909
962
|
if (!ingress) return socket.destroy();
|
|
910
|
-
federation.forwardUpgrade({ req, socket, head, nodesPath, localPort: runtimePort, localCredential: () => tokenHolder.value, ingress, readonly: proxyReadonly, activeSockets: proxySockets });
|
|
963
|
+
federation.forwardUpgrade({ req, socket, head, nodesPath, localPort: runtimePort, localCredential: () => tokenHolder.value, ingress, readonly: proxyReadonly, activeSockets: proxySockets, hopSecret: () => hopSecret });
|
|
911
964
|
return;
|
|
912
965
|
}
|
|
913
966
|
if (pathname === '/node' || pathname.startsWith('/node/')) {
|
|
@@ -933,10 +986,19 @@ function createServer(opts = {}) {
|
|
|
933
986
|
// non possono settare header sui WS -> il flusso locale resta identico
|
|
934
987
|
// (token nel frame attach, mai in URL).
|
|
935
988
|
const preauth = req ? verify(tokenHolder.value, bearerFrom(req)) : false;
|
|
989
|
+
// Scope celle sull'ATTACH. Calcolato in modo sincrono di proposito: rendere
|
|
990
|
+
// async questo handler rischierebbe di perdere i frame che arrivano prima
|
|
991
|
+
// di bindWs. `/ws` attacca per NOME DI SESSIONE e senza questo gate ogni
|
|
992
|
+
// filtro sugli elenchi sarebbe decorativo — basterebbe indovinare
|
|
993
|
+
// `cloud-Dev`.
|
|
994
|
+
const wsScope = wsCellScope(req);
|
|
936
995
|
bindWs(ws, {
|
|
937
996
|
openAttach,
|
|
938
997
|
verifyToken: (t) => preauth || verify(tokenHolder.value, t),
|
|
939
|
-
|
|
998
|
+
// Una sessione fuori scope viene trattata come inesistente: il client
|
|
999
|
+
// riceve 4404, lo stesso codice di una sessione che non c'e'. Dire "esiste
|
|
1000
|
+
// ma non puoi" rivelerebbe proprio cio' che lo scope nasconde.
|
|
1001
|
+
isValidSession: (name) => sessionExistsImpl(name) && wsScope.allowsSession(name),
|
|
940
1002
|
runAction: (sess, action) => runAction(cfg.tmuxBin, sess, action),
|
|
941
1003
|
countClients: (sess) => attachedClients(cfg.tmuxBin, sess),
|
|
942
1004
|
defaults: { readonlyDefault: cfg.readonlyDefault, tmuxBin: cfg.tmuxBin },
|
package/package.json
CHANGED