@xenon-device-management/xenon 1.22.0 → 1.22.2

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.
Files changed (82) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/public/assets/{ApiKeyGate-B_1qvA7M.js → ApiKeyGate-Snh2iZH5.js} +1 -1
  3. package/lib/public/assets/{BugReportButton-0m4q010L.js → BugReportButton-Sk0KGufH.js} +1 -1
  4. package/lib/public/assets/DeviceMosaicView-O292tZkA.js +2 -0
  5. package/lib/public/assets/{EmptyState-CTmGgoSm.js → EmptyState-CQun3BJE.js} +1 -1
  6. package/lib/public/assets/{FieldGroup-oWAJBBAu.js → FieldGroup-DmYZP2j7.js} +1 -1
  7. package/lib/public/assets/{Modal-LXKxgyV7.js → Modal-C5W8lMJ6.js} +1 -1
  8. package/lib/public/assets/{SegmentedControl-BSWMM3qE.js → SegmentedControl-BIzlKyUd.js} +1 -1
  9. package/lib/public/assets/{SettingCard-CeHOIGW9.js → SettingCard-DAFqQw7S.js} +1 -1
  10. package/lib/public/assets/{Table-EHWJCCzN.js → Table-DRkkM4OR.js} +1 -1
  11. package/lib/public/assets/{activity-CHfTPuBn.js → activity-B6x69oOy.js} +1 -1
  12. package/lib/public/assets/{ai-settings-B_dT4ctk.js → ai-settings-CSZQis7F.js} +1 -1
  13. package/lib/public/assets/{api-keys-BIKaKigW.js → api-keys-4ddjSngL.js} +1 -1
  14. package/lib/public/assets/{apps-Dp4crlsn.js → apps-CePIfnu9.js} +1 -1
  15. package/lib/public/assets/{arrow-left-QDqb-yc1.js → arrow-left-CaOe18yC.js} +1 -1
  16. package/lib/public/assets/{arrow-right-D_onnhWU.js → arrow-right-FEQsqDvH.js} +1 -1
  17. package/lib/public/assets/{arrow-up-right-DOwr8XxB.js → arrow-up-right-Dnszb73S.js} +1 -1
  18. package/lib/public/assets/{auth-shell-NbHUThYR.js → auth-shell-Vq1rYIVD.js} +2 -2
  19. package/lib/public/assets/{builds-page-CQseApdL.js → builds-page-FfsUegKZ.js} +1 -1
  20. package/lib/public/assets/{button-Do8mBvkU.js → button-DrqhMLZ0.js} +1 -1
  21. package/lib/public/assets/{calendar-DrH0mjSD.js → calendar-BuRuUa7q.js} +1 -1
  22. package/lib/public/assets/{check-C8Un1cwf.js → check-DYccNk73.js} +1 -1
  23. package/lib/public/assets/{chevron-right-DHjIgZvn.js → chevron-right-C6ny35i3.js} +1 -1
  24. package/lib/public/assets/{circle-check-Dkg-Ka4_.js → circle-check-Cokzy9a2.js} +1 -1
  25. package/lib/public/assets/{circle-x-BPKWtaZx.js → circle-x-BhUzyxKR.js} +1 -1
  26. package/lib/public/assets/{clock-CQLUBaRx.js → clock-BCG0drfS.js} +1 -1
  27. package/lib/public/assets/{copy-CWx0Ec1c.js → copy-BKOm9hko.js} +1 -1
  28. package/lib/public/assets/{device-explorer-BjrTj6Sm.js → device-explorer-CIjs3BXP.js} +1 -1
  29. package/lib/public/assets/{download-DJ_pjhGv.js → download-R2k7Nd4P.js} +1 -1
  30. package/lib/public/assets/{forgot-password-MmPdS0S2.js → forgot-password-CiZcr-gB.js} +1 -1
  31. package/lib/public/assets/{funnel-DR8YKoFW.js → funnel-SD4F5IdR.js} +1 -1
  32. package/lib/public/assets/{index-DbzQAv86.js → index-BHDlQcd6.js} +2 -2
  33. package/lib/public/assets/{input-CKKtQEU5.js → input-Ilib6feD.js} +1 -1
  34. package/lib/public/assets/{layers-DprXM0fz.js → layers-CLuKN5Oy.js} +1 -1
  35. package/lib/public/assets/{lock-BXGh7bJZ.js → lock-s9ZhGDFF.js} +1 -1
  36. package/lib/public/assets/{login-DOYeWP9H.js → login-EkaxK7K5.js} +1 -1
  37. package/lib/public/assets/{maintenance-settings-w8U3BRZk.js → maintenance-settings-yYhZaU7n.js} +1 -1
  38. package/lib/public/assets/{mouse-pointer-2-BD4cWXDU.js → mouse-pointer-2-C6yy1DTx.js} +1 -1
  39. package/lib/public/assets/{network-C5bJeATc.js → network-B8fPwmW9.js} +1 -1
  40. package/lib/public/assets/{overview-DzAXi4vj.js → overview-DrTe5eJi.js} +1 -1
  41. package/lib/public/assets/{page-header-CqaoOYPH.js → page-header-DhD-Kua9.js} +1 -1
  42. package/lib/public/assets/{plus-CJfVcjJv.js → plus-B75ovH3O.js} +1 -1
  43. package/lib/public/assets/{profile-page-Mk1yk1c4.js → profile-page-C2p5rB3x.js} +1 -1
  44. package/lib/public/assets/{reset-password-D1gtSgzr.js → reset-password-tCemYrqV.js} +1 -1
  45. package/lib/public/assets/{runbook-page-B7EjVqsY.js → runbook-page-Dq3wdSII.js} +1 -1
  46. package/lib/public/assets/{select-DVYczzD1.js → select-Cfv-njLE.js} +1 -1
  47. package/lib/public/assets/{selector-detail-page-C6b-Z-ep.js → selector-detail-page-8xf4CxH5.js} +1 -1
  48. package/lib/public/assets/{selector-health-DhzDZLlF.js → selector-health-BP45B44s.js} +1 -1
  49. package/lib/public/assets/{selector-health-page-w5zIE-r8.js → selector-health-page-D-8SdN_l.js} +1 -1
  50. package/lib/public/assets/{session-detail-page-CiPVLY-1.js → session-detail-page-Dm4tnoTT.js} +1 -1
  51. package/lib/public/assets/{settings-Bm-LdOxO.js → settings-ChfaAd8F.js} +1 -1
  52. package/lib/public/assets/{status-pill-outline-BgQ_Ur6B.js → status-pill-outline-CLa0C1Sg.js} +1 -1
  53. package/lib/public/assets/{tablet-1GdHxaSx.js → tablet-0sIFwy2Z.js} +1 -1
  54. package/lib/public/assets/{teams-D8rZexDl.js → teams-BFhWbK5i.js} +1 -1
  55. package/lib/public/assets/{trash-2-DJgpvIoq.js → trash-2-BcQxmJP-.js} +1 -1
  56. package/lib/public/assets/{upload-BJ0V1QbY.js → upload-BucqEWub.js} +1 -1
  57. package/lib/public/assets/{use-password-reset-mode-BVFQr1mo.js → use-password-reset-mode-XoxXVCzm.js} +1 -1
  58. package/lib/public/assets/{users-Bxnci4rK.js → users-B7uveXL_.js} +1 -1
  59. package/lib/public/assets/{users-DUb_rmxk.js → users-DyB6E3FF.js} +1 -1
  60. package/lib/public/assets/{webhook-settings-DL54Ijod.js → webhook-settings-qoHAETzC.js} +1 -1
  61. package/lib/public/assets/{zap-BLSqrVs9.js → zap-CqGOjAjn.js} +1 -1
  62. package/lib/public/index.html +1 -1
  63. package/lib/src/app/routers/control.js +18 -15
  64. package/lib/src/app/routers/recordings.js +5 -3
  65. package/lib/src/services/VideoPipelineService.js +44 -14
  66. package/lib/src/services/fillMissingScreenSize.js +50 -0
  67. package/lib/src/services/recording/RecordingOrchestrator.js +103 -2
  68. package/lib/src/services/recording/activeRecordings.js +8 -2
  69. package/lib/src/services/recording/annotation-render.js +189 -47
  70. package/lib/src/services/recording/probeDuration.js +21 -0
  71. package/lib/src/services/recording/proof-bundle.js +38 -15
  72. package/lib/src/services/recording/recordingTiming.js +86 -0
  73. package/lib/test/unit/active-recordings.spec.js +8 -0
  74. package/lib/test/unit/annotation-render.spec.js +226 -0
  75. package/lib/test/unit/composite-geometry.spec.js +80 -0
  76. package/lib/test/unit/fill-missing-screen-size.spec.js +65 -0
  77. package/lib/test/unit/proof-bundle.spec.js +65 -0
  78. package/lib/test/unit/recording-orchestrator.spec.js +95 -0
  79. package/lib/test/unit/recording-timing.spec.js +88 -0
  80. package/lib/tsconfig.tsbuildinfo +1 -1
  81. package/package.json +1 -1
  82. package/lib/public/assets/DeviceMosaicView-D4uKl1Gv.js +0 -2
@@ -1 +1 @@
1
- import{r as h,p as y,X as p,j as e,u as E,T as R,q as T,s as H}from"./index-DbzQAv86.js";import{p as M}from"./labels-C21SA4JG.js";import{F as L}from"./funnel-DR8YKoFW.js";import{A as O}from"./arrow-right-D_onnhWU.js";import{T as B,M as P}from"./tablet-1GdHxaSx.js";import{A as U}from"./activity-CHfTPuBn.js";import{P as X}from"./page-header-CqaoOYPH.js";const k=20;function G(t){return t.deviceName||(t.deviceUdid?t.deviceUdid.slice(0,8):"device")}function C(t){const n=t.commandName||"selector",i=G(t);return t.tier?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:t.tier})," healed ",e.jsx("strong",{children:n})," on ",e.jsx("strong",{children:i})]}):e.jsxs(e.Fragment,{children:["Healed ",e.jsx("strong",{children:n})," on ",e.jsx("strong",{children:i})]})}function K(){const[t,n]=h.useState([]),[i,x]=h.useState([]),[u,g]=h.useState(0),[a,j]=h.useState([]),{on:m}=y(),F=h.useRef(0);h.useEffect(()=>{let o=!1;const f=()=>{p.getDevices().then(c=>{!o&&Array.isArray(c)&&n(c)}).catch(()=>{})};f();const w=setInterval(f,3e4);return p.getSessions().then(c=>{!o&&Array.isArray(c)&&x(c)}).catch(()=>{}),p.getPendingSessionsCount().then(c=>{if(o)return;const b=typeof c=="number"?c:typeof(c==null?void 0:c.count)=="number"?c.count:0;g(b)}).catch(()=>{}),p.getRecentHealingEvents().then(c=>{if(o||!c)return;const b=Array.isArray(c.events)?c.events:[];b.length!==0&&j(N=>{const S=new Set(N.map(r=>r.id));return[...b.filter(r=>!S.has(r.id)).map(r=>({id:r.id,ts:Date.parse(r.createdAt)||Date.now(),kind:"heal",message:C(r)})),...N].sort((r,l)=>l.ts-r.ts).slice(0,k)})}).catch(()=>{}),()=>{o=!0,clearInterval(w)}},[]),h.useEffect(()=>{const o=[],f=(s,r)=>{F.current+=1;const l={id:`${Date.now()}-${F.current}`,ts:Date.now(),kind:s,message:r};j(d=>[l,...d].slice(0,k))},w=s=>{var d;s&&s.session&&x(v=>v.find($=>$.id===s.session.id)?v:[s.session,...v]);const r=((d=s==null?void 0:s.session)==null?void 0:d.id)||(s==null?void 0:s.sessionId)||"",l=r?r.slice(0,6):"";f("session",e.jsxs(e.Fragment,{children:["Session ",e.jsx("strong",{children:l||"—"})," started"]}))},c=s=>{var v,A;const r=((v=s==null?void 0:s.session)==null?void 0:v.id)||(s==null?void 0:s.sessionId)||"",l=!!((A=s==null?void 0:s.session)!=null&&A.failure_reason||s!=null&&s.failure_reason);r&&x($=>$.map(D=>D.id===r?{...D,status:l?"failed":"finished"}:D));const d=r?r.slice(0,6):"";f(l?"session-failed":"session-end",l?e.jsxs(e.Fragment,{children:["Session ",e.jsx("strong",{children:d||"—"})," failed"]}):e.jsxs(e.Fragment,{children:["Session ",e.jsx("strong",{children:d||"—"})," ended"]}))},b=s=>{const r=(s==null?void 0:s.nodeId)||(s==null?void 0:s.id)||"node";f("node",e.jsxs(e.Fragment,{children:["Node ",e.jsx("strong",{children:String(r)})," connected"]}))},N=s=>{const r=(s==null?void 0:s.nodeId)||(s==null?void 0:s.id)||"node";f("node",e.jsxs(e.Fragment,{children:["Node ",e.jsx("strong",{children:String(r)})," disconnected"]}))},S=s=>{const r=s;if(!r||!r.id)return;const l={id:r.id,ts:Date.parse(r.createdAt)||Date.now(),kind:"heal",message:C(r)};j(d=>d.some(v=>v.id===l.id)?d:[l,...d].slice(0,k))};return o.push(m("session_started",w)),o.push(m("session_stopped",c)),o.push(m("node_connected",b)),o.push(m("node_disconnected",N)),o.push(m("healing_event",S)),()=>{o.forEach(s=>s&&s())}},[m]);const q=i.filter(o=>o.status==="running");return{devices:t,activeSessions:q,queuedSessions:u,activity:a}}const Q={healthy:"bg-[var(--color-success)]",neutral:"bg-[var(--border-strong)]",warn:"bg-[var(--amber)]",critical:"bg-[var(--red)]"},V={healthy:"bg-[var(--color-success)]",neutral:"bg-[var(--text-dim)]",warn:"bg-[var(--amber)]",critical:"bg-[var(--red)]"},Y={healthy:"text-[var(--color-success)]",neutral:"text-[var(--text-muted)]",warn:"text-[var(--amber)]",critical:"text-[var(--red)]"},I=({label:t,value:n,secondaryValue:i,subtitle:x,state:u="neutral"})=>e.jsxs("div",{className:"relative overflow-hidden rounded-lg border border-[var(--border)] bg-[var(--surface)] p-4 hover:border-[var(--border-strong)] transition-colors",children:[e.jsx("span",{className:`absolute left-0 top-0 bottom-0 w-[2px] ${Q[u]}`}),e.jsx("div",{className:"text-[11px] font-semibold text-[var(--text-muted)]",children:t}),e.jsxs("div",{className:"mt-3 flex items-baseline gap-2",children:[e.jsx("span",{className:"text-3xl font-semibold text-[var(--text)] tabular-nums",children:n}),i!=null&&e.jsxs("span",{className:"text-sm font-mono text-[var(--text-dim)]",children:["/ ",i]})]}),e.jsxs("div",{className:`mt-3 flex items-center gap-1.5 text-xs ${Y[u]}`,children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${V[u]}`}),e.jsx("span",{children:x})]})]});function _(t){return t.offline?"offline":t.reservedBy?"reserved":t.busy?"busy":"ready"}function z(t){const n=(t.platform||"").toLowerCase();return n==="tvos"?R:/pad|tablet/i.test(t.name||"")?B:n==="ios"||n==="android"?T:P}const J={ready:{dot:"bg-[var(--color-success)]",text:"text-[var(--color-success)]",label:"Ready"},busy:{dot:"bg-[var(--amber)]",text:"text-[var(--amber)]",label:"Busy"},reserved:{dot:"bg-[var(--blue)]",text:"text-[var(--blue)]",label:"Reserved"},offline:{dot:"bg-[var(--text-dim)]",text:"text-[var(--text-dim)]",label:"Offline"}};function W(t){return t?t.length<=16?t:`${t.slice(0,8)}…${t.slice(-4)}`:""}const Z=({devices:t,maxRows:n=6})=>{const[i,x]=h.useState("all"),u=E(),g=(i==="all"?t:t.filter(a=>_(a)!=="ready")).slice(0,n);return e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] flex flex-col",children:[e.jsxs("header",{className:"flex items-center justify-between px-4 py-3 border-b border-[var(--border)]",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--text)]",children:"Fleet status"}),e.jsxs("span",{className:"text-xs font-mono text-[var(--text-dim)]",children:[t.length," devices"]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("div",{role:"group","aria-label":"Filter devices",className:"flex items-center gap-0.5 bg-[var(--surface-2)] border border-[var(--border)] rounded-md p-0.5",children:[e.jsx("button",{type:"button",onClick:()=>x("all"),"aria-pressed":i==="all",className:`px-2 py-1 text-[11px] rounded transition-colors ${i==="all"?"bg-[var(--border-strong)] text-[var(--text)]":"text-[var(--text-dim)] hover:text-[var(--text)]"}`,children:"All"}),e.jsxs("button",{type:"button",onClick:()=>x("issues"),"aria-pressed":i==="issues",className:`px-2 py-1 text-[11px] rounded transition-colors flex items-center gap-1 ${i==="issues"?"bg-[var(--border-strong)] text-[var(--text)]":"text-[var(--text-dim)] hover:text-[var(--text)]"}`,children:[e.jsx(L,{className:"h-3 w-3"}),"Non-ready"]})]}),e.jsxs("button",{type:"button",onClick:()=>u("/devices"),className:"ml-1 inline-flex items-center gap-1 text-xs text-[var(--text-muted)] hover:text-[var(--text)] transition-colors",children:["View all ",e.jsx(O,{className:"h-3 w-3"})]})]})]}),e.jsx("div",{className:"divide-y divide-[var(--border)]",children:g.length===0?e.jsx("div",{className:"px-4 py-8 text-center text-xs text-[var(--text-dim)]",children:i==="issues"?"All devices are ready.":"No devices registered."}):g.map(a=>{const j=z(a),m=J[_(a)];return e.jsxs("button",{type:"button",onClick:()=>a.udid&&u(`/devices/${a.udid}/control/screen`),className:"w-full flex items-center gap-3 px-4 py-2.5 hover:bg-[var(--surface-2)] transition-colors text-left",children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full shrink-0 ${m.dot}`}),e.jsx(j,{className:"h-4 w-4 text-[var(--text-dim)] shrink-0"}),e.jsxs("div",{className:"flex-1 min-w-0 flex items-center gap-3",children:[e.jsx("div",{className:"min-w-0 flex-1",children:e.jsx("div",{className:"text-xs text-[var(--text)] truncate",children:a.name||a.udid})}),e.jsxs("div",{className:"hidden md:block text-[11px] font-mono text-[var(--text-dim)] w-24 text-right",children:[M(a.platform)," ",a.sdk||""]}),e.jsx("div",{className:"hidden lg:block text-[11px] font-mono text-[var(--text-dim)] w-32 text-right",children:W(a.udid)})]}),e.jsx("span",{className:`font-mono text-[10px] font-semibold tracking-wider w-20 text-right ${m.text}`,children:m.label})]},a.udid||a.name)})})]})},ee={session:"bg-[var(--color-accent)]","session-end":"bg-[var(--text-dim)]","session-failed":"bg-[var(--red)]",node:"bg-[var(--blue)]",heal:"bg-[var(--amber)]"};function se(t){const n=new Date(t);return`${String(n.getHours()).padStart(2,"0")}:${String(n.getMinutes()).padStart(2,"0")}`}const te=({events:t,live:n=!0})=>e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] flex flex-col",children:[e.jsx("header",{className:"flex items-center justify-between px-4 py-3 border-b border-[var(--border)]",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--text)]",children:"Recent activity"}),e.jsxs("span",{className:"flex items-center gap-1 text-[11px] text-[var(--text-dim)]",children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${n?"bg-[var(--color-success)] pulse-dot":"bg-[var(--text-dim)]"}`}),n?"Live":"Offline"]})]})}),t.length===0?e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center px-6 py-10 text-center gap-2",children:[e.jsx("div",{className:"h-10 w-10 rounded-full bg-[var(--surface-2)] border border-[var(--border)] flex items-center justify-center",children:e.jsx(U,{className:"h-5 w-5 text-[var(--text-dim)]"})}),e.jsx("div",{className:"text-sm font-medium text-[var(--text)]",children:"No activity yet"}),e.jsx("div",{className:"text-xs text-[var(--text-dim)] max-w-xs",children:"Session and heal events will appear here as they happen."})]}):e.jsx("div",{className:"flex-1 overflow-y-auto divide-y divide-[var(--border)]",children:t.map(i=>e.jsxs("div",{className:"flex items-center gap-3 px-4 py-2.5",children:[e.jsx("span",{className:"font-mono text-[10px] text-[var(--text-dim)] w-10 shrink-0",children:se(i.ts)}),e.jsx("span",{className:`h-1.5 w-1.5 rounded-full shrink-0 ${ee[i.kind]}`}),e.jsx("span",{className:"text-xs text-[var(--text-muted)] truncate",children:i.message})]},i.id))})]}),re="—",xe=()=>{const t=K(),n=t.devices.length,i=t.devices.filter(g=>!g.offline).length,x=n===0?"neutral":i===n?"healthy":"warn",u=t.activeSessions.length>0?"healthy":t.queuedSessions>0?"warn":"neutral";return e.jsxs("div",{className:"flex-1 min-w-0 overflow-y-auto",children:[e.jsx(X,{icon:H,title:"Overview",subtitle:"Devices, sessions and healing activity across your lab."}),e.jsxs("div",{className:"max-w-7xl mx-auto px-6 py-6",children:[e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6",children:[e.jsx(I,{label:"Devices online",value:n===0?re:i,secondaryValue:n===0?void 0:n,subtitle:n===0?"No devices registered":`${i} of ${n} online`,state:x}),e.jsx(I,{label:"Active sessions",value:t.activeSessions.length,subtitle:t.queuedSessions>0?`${t.queuedSessions} queued`:"No queue",state:u})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-3",children:[e.jsx(Z,{devices:t.devices}),e.jsx(te,{events:t.activity,live:!0})]})]})]})};export{xe as default};
1
+ import{r as h,p as y,X as p,j as e,u as E,T as R,q as T,s as H}from"./index-BHDlQcd6.js";import{p as M}from"./labels-C21SA4JG.js";import{F as L}from"./funnel-SD4F5IdR.js";import{A as O}from"./arrow-right-FEQsqDvH.js";import{T as B,M as P}from"./tablet-0sIFwy2Z.js";import{A as U}from"./activity-B6x69oOy.js";import{P as X}from"./page-header-DhD-Kua9.js";const k=20;function G(t){return t.deviceName||(t.deviceUdid?t.deviceUdid.slice(0,8):"device")}function C(t){const n=t.commandName||"selector",i=G(t);return t.tier?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:t.tier})," healed ",e.jsx("strong",{children:n})," on ",e.jsx("strong",{children:i})]}):e.jsxs(e.Fragment,{children:["Healed ",e.jsx("strong",{children:n})," on ",e.jsx("strong",{children:i})]})}function K(){const[t,n]=h.useState([]),[i,x]=h.useState([]),[u,g]=h.useState(0),[a,j]=h.useState([]),{on:m}=y(),F=h.useRef(0);h.useEffect(()=>{let o=!1;const f=()=>{p.getDevices().then(c=>{!o&&Array.isArray(c)&&n(c)}).catch(()=>{})};f();const w=setInterval(f,3e4);return p.getSessions().then(c=>{!o&&Array.isArray(c)&&x(c)}).catch(()=>{}),p.getPendingSessionsCount().then(c=>{if(o)return;const b=typeof c=="number"?c:typeof(c==null?void 0:c.count)=="number"?c.count:0;g(b)}).catch(()=>{}),p.getRecentHealingEvents().then(c=>{if(o||!c)return;const b=Array.isArray(c.events)?c.events:[];b.length!==0&&j(N=>{const S=new Set(N.map(r=>r.id));return[...b.filter(r=>!S.has(r.id)).map(r=>({id:r.id,ts:Date.parse(r.createdAt)||Date.now(),kind:"heal",message:C(r)})),...N].sort((r,l)=>l.ts-r.ts).slice(0,k)})}).catch(()=>{}),()=>{o=!0,clearInterval(w)}},[]),h.useEffect(()=>{const o=[],f=(s,r)=>{F.current+=1;const l={id:`${Date.now()}-${F.current}`,ts:Date.now(),kind:s,message:r};j(d=>[l,...d].slice(0,k))},w=s=>{var d;s&&s.session&&x(v=>v.find($=>$.id===s.session.id)?v:[s.session,...v]);const r=((d=s==null?void 0:s.session)==null?void 0:d.id)||(s==null?void 0:s.sessionId)||"",l=r?r.slice(0,6):"";f("session",e.jsxs(e.Fragment,{children:["Session ",e.jsx("strong",{children:l||"—"})," started"]}))},c=s=>{var v,A;const r=((v=s==null?void 0:s.session)==null?void 0:v.id)||(s==null?void 0:s.sessionId)||"",l=!!((A=s==null?void 0:s.session)!=null&&A.failure_reason||s!=null&&s.failure_reason);r&&x($=>$.map(D=>D.id===r?{...D,status:l?"failed":"finished"}:D));const d=r?r.slice(0,6):"";f(l?"session-failed":"session-end",l?e.jsxs(e.Fragment,{children:["Session ",e.jsx("strong",{children:d||"—"})," failed"]}):e.jsxs(e.Fragment,{children:["Session ",e.jsx("strong",{children:d||"—"})," ended"]}))},b=s=>{const r=(s==null?void 0:s.nodeId)||(s==null?void 0:s.id)||"node";f("node",e.jsxs(e.Fragment,{children:["Node ",e.jsx("strong",{children:String(r)})," connected"]}))},N=s=>{const r=(s==null?void 0:s.nodeId)||(s==null?void 0:s.id)||"node";f("node",e.jsxs(e.Fragment,{children:["Node ",e.jsx("strong",{children:String(r)})," disconnected"]}))},S=s=>{const r=s;if(!r||!r.id)return;const l={id:r.id,ts:Date.parse(r.createdAt)||Date.now(),kind:"heal",message:C(r)};j(d=>d.some(v=>v.id===l.id)?d:[l,...d].slice(0,k))};return o.push(m("session_started",w)),o.push(m("session_stopped",c)),o.push(m("node_connected",b)),o.push(m("node_disconnected",N)),o.push(m("healing_event",S)),()=>{o.forEach(s=>s&&s())}},[m]);const q=i.filter(o=>o.status==="running");return{devices:t,activeSessions:q,queuedSessions:u,activity:a}}const Q={healthy:"bg-[var(--color-success)]",neutral:"bg-[var(--border-strong)]",warn:"bg-[var(--amber)]",critical:"bg-[var(--red)]"},V={healthy:"bg-[var(--color-success)]",neutral:"bg-[var(--text-dim)]",warn:"bg-[var(--amber)]",critical:"bg-[var(--red)]"},Y={healthy:"text-[var(--color-success)]",neutral:"text-[var(--text-muted)]",warn:"text-[var(--amber)]",critical:"text-[var(--red)]"},I=({label:t,value:n,secondaryValue:i,subtitle:x,state:u="neutral"})=>e.jsxs("div",{className:"relative overflow-hidden rounded-lg border border-[var(--border)] bg-[var(--surface)] p-4 hover:border-[var(--border-strong)] transition-colors",children:[e.jsx("span",{className:`absolute left-0 top-0 bottom-0 w-[2px] ${Q[u]}`}),e.jsx("div",{className:"text-[11px] font-semibold text-[var(--text-muted)]",children:t}),e.jsxs("div",{className:"mt-3 flex items-baseline gap-2",children:[e.jsx("span",{className:"text-3xl font-semibold text-[var(--text)] tabular-nums",children:n}),i!=null&&e.jsxs("span",{className:"text-sm font-mono text-[var(--text-dim)]",children:["/ ",i]})]}),e.jsxs("div",{className:`mt-3 flex items-center gap-1.5 text-xs ${Y[u]}`,children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${V[u]}`}),e.jsx("span",{children:x})]})]});function _(t){return t.offline?"offline":t.reservedBy?"reserved":t.busy?"busy":"ready"}function z(t){const n=(t.platform||"").toLowerCase();return n==="tvos"?R:/pad|tablet/i.test(t.name||"")?B:n==="ios"||n==="android"?T:P}const J={ready:{dot:"bg-[var(--color-success)]",text:"text-[var(--color-success)]",label:"Ready"},busy:{dot:"bg-[var(--amber)]",text:"text-[var(--amber)]",label:"Busy"},reserved:{dot:"bg-[var(--blue)]",text:"text-[var(--blue)]",label:"Reserved"},offline:{dot:"bg-[var(--text-dim)]",text:"text-[var(--text-dim)]",label:"Offline"}};function W(t){return t?t.length<=16?t:`${t.slice(0,8)}…${t.slice(-4)}`:""}const Z=({devices:t,maxRows:n=6})=>{const[i,x]=h.useState("all"),u=E(),g=(i==="all"?t:t.filter(a=>_(a)!=="ready")).slice(0,n);return e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] flex flex-col",children:[e.jsxs("header",{className:"flex items-center justify-between px-4 py-3 border-b border-[var(--border)]",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--text)]",children:"Fleet status"}),e.jsxs("span",{className:"text-xs font-mono text-[var(--text-dim)]",children:[t.length," devices"]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("div",{role:"group","aria-label":"Filter devices",className:"flex items-center gap-0.5 bg-[var(--surface-2)] border border-[var(--border)] rounded-md p-0.5",children:[e.jsx("button",{type:"button",onClick:()=>x("all"),"aria-pressed":i==="all",className:`px-2 py-1 text-[11px] rounded transition-colors ${i==="all"?"bg-[var(--border-strong)] text-[var(--text)]":"text-[var(--text-dim)] hover:text-[var(--text)]"}`,children:"All"}),e.jsxs("button",{type:"button",onClick:()=>x("issues"),"aria-pressed":i==="issues",className:`px-2 py-1 text-[11px] rounded transition-colors flex items-center gap-1 ${i==="issues"?"bg-[var(--border-strong)] text-[var(--text)]":"text-[var(--text-dim)] hover:text-[var(--text)]"}`,children:[e.jsx(L,{className:"h-3 w-3"}),"Non-ready"]})]}),e.jsxs("button",{type:"button",onClick:()=>u("/devices"),className:"ml-1 inline-flex items-center gap-1 text-xs text-[var(--text-muted)] hover:text-[var(--text)] transition-colors",children:["View all ",e.jsx(O,{className:"h-3 w-3"})]})]})]}),e.jsx("div",{className:"divide-y divide-[var(--border)]",children:g.length===0?e.jsx("div",{className:"px-4 py-8 text-center text-xs text-[var(--text-dim)]",children:i==="issues"?"All devices are ready.":"No devices registered."}):g.map(a=>{const j=z(a),m=J[_(a)];return e.jsxs("button",{type:"button",onClick:()=>a.udid&&u(`/devices/${a.udid}/control/screen`),className:"w-full flex items-center gap-3 px-4 py-2.5 hover:bg-[var(--surface-2)] transition-colors text-left",children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full shrink-0 ${m.dot}`}),e.jsx(j,{className:"h-4 w-4 text-[var(--text-dim)] shrink-0"}),e.jsxs("div",{className:"flex-1 min-w-0 flex items-center gap-3",children:[e.jsx("div",{className:"min-w-0 flex-1",children:e.jsx("div",{className:"text-xs text-[var(--text)] truncate",children:a.name||a.udid})}),e.jsxs("div",{className:"hidden md:block text-[11px] font-mono text-[var(--text-dim)] w-24 text-right",children:[M(a.platform)," ",a.sdk||""]}),e.jsx("div",{className:"hidden lg:block text-[11px] font-mono text-[var(--text-dim)] w-32 text-right",children:W(a.udid)})]}),e.jsx("span",{className:`font-mono text-[10px] font-semibold tracking-wider w-20 text-right ${m.text}`,children:m.label})]},a.udid||a.name)})})]})},ee={session:"bg-[var(--color-accent)]","session-end":"bg-[var(--text-dim)]","session-failed":"bg-[var(--red)]",node:"bg-[var(--blue)]",heal:"bg-[var(--amber)]"};function se(t){const n=new Date(t);return`${String(n.getHours()).padStart(2,"0")}:${String(n.getMinutes()).padStart(2,"0")}`}const te=({events:t,live:n=!0})=>e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] flex flex-col",children:[e.jsx("header",{className:"flex items-center justify-between px-4 py-3 border-b border-[var(--border)]",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--text)]",children:"Recent activity"}),e.jsxs("span",{className:"flex items-center gap-1 text-[11px] text-[var(--text-dim)]",children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${n?"bg-[var(--color-success)] pulse-dot":"bg-[var(--text-dim)]"}`}),n?"Live":"Offline"]})]})}),t.length===0?e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center px-6 py-10 text-center gap-2",children:[e.jsx("div",{className:"h-10 w-10 rounded-full bg-[var(--surface-2)] border border-[var(--border)] flex items-center justify-center",children:e.jsx(U,{className:"h-5 w-5 text-[var(--text-dim)]"})}),e.jsx("div",{className:"text-sm font-medium text-[var(--text)]",children:"No activity yet"}),e.jsx("div",{className:"text-xs text-[var(--text-dim)] max-w-xs",children:"Session and heal events will appear here as they happen."})]}):e.jsx("div",{className:"flex-1 overflow-y-auto divide-y divide-[var(--border)]",children:t.map(i=>e.jsxs("div",{className:"flex items-center gap-3 px-4 py-2.5",children:[e.jsx("span",{className:"font-mono text-[10px] text-[var(--text-dim)] w-10 shrink-0",children:se(i.ts)}),e.jsx("span",{className:`h-1.5 w-1.5 rounded-full shrink-0 ${ee[i.kind]}`}),e.jsx("span",{className:"text-xs text-[var(--text-muted)] truncate",children:i.message})]},i.id))})]}),re="—",xe=()=>{const t=K(),n=t.devices.length,i=t.devices.filter(g=>!g.offline).length,x=n===0?"neutral":i===n?"healthy":"warn",u=t.activeSessions.length>0?"healthy":t.queuedSessions>0?"warn":"neutral";return e.jsxs("div",{className:"flex-1 min-w-0 overflow-y-auto",children:[e.jsx(X,{icon:H,title:"Overview",subtitle:"Devices, sessions and healing activity across your lab."}),e.jsxs("div",{className:"max-w-7xl mx-auto px-6 py-6",children:[e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6",children:[e.jsx(I,{label:"Devices online",value:n===0?re:i,secondaryValue:n===0?void 0:n,subtitle:n===0?"No devices registered":`${i} of ${n} online`,state:x}),e.jsx(I,{label:"Active sessions",value:t.activeSessions.length,subtitle:t.queuedSessions>0?`${t.queuedSessions} queued`:"No queue",state:u})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-3",children:[e.jsx(Z,{devices:t.devices}),e.jsx(te,{events:t.activity,live:!0})]})]})]})};export{xe as default};
@@ -1 +1 @@
1
- import{j as e}from"./index-DbzQAv86.js";const i=({icon:s,children:t})=>e.jsxs("h1",{className:"flex items-center gap-2 text-xl font-semibold tracking-tight text-[var(--text)]",children:[s&&e.jsx(s,{className:"h-5 w-5 text-[var(--color-accent)] shrink-0","aria-hidden":"true"}),e.jsx("span",{className:"truncate",children:t})]}),l=({icon:s,eyebrow:t,title:x,subtitle:a,action:r})=>e.jsx("header",{className:"px-6 pt-6 pb-4 border-b border-[var(--border)]",children:e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[t&&e.jsx("div",{className:"text-[11px] font-semibold text-[var(--text-dim)] mb-1.5",children:t}),e.jsx(i,{icon:s,children:x}),a&&e.jsx("p",{className:"mt-1.5 text-sm text-[var(--text-muted)] max-w-3xl",children:a})]}),r&&e.jsx("div",{className:"flex items-center gap-2 shrink-0",children:r})]})});export{l as P,i as a};
1
+ import{j as e}from"./index-BHDlQcd6.js";const i=({icon:s,children:t})=>e.jsxs("h1",{className:"flex items-center gap-2 text-xl font-semibold tracking-tight text-[var(--text)]",children:[s&&e.jsx(s,{className:"h-5 w-5 text-[var(--color-accent)] shrink-0","aria-hidden":"true"}),e.jsx("span",{className:"truncate",children:t})]}),l=({icon:s,eyebrow:t,title:x,subtitle:a,action:r})=>e.jsx("header",{className:"px-6 pt-6 pb-4 border-b border-[var(--border)]",children:e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[t&&e.jsx("div",{className:"text-[11px] font-semibold text-[var(--text-dim)] mb-1.5",children:t}),e.jsx(i,{icon:s,children:x}),a&&e.jsx("p",{className:"mt-1.5 text-sm text-[var(--text-muted)] max-w-3xl",children:a})]}),r&&e.jsx("div",{className:"flex items-center gap-2 shrink-0",children:r})]})});export{l as P,i as a};
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-DbzQAv86.js";/**
1
+ import{c as e}from"./index-BHDlQcd6.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as S,b as N,r as n,j as e,Z as E,D as T}from"./index-DbzQAv86.js";import{P as C}from"./page-header-CqaoOYPH.js";import{a as y,f as w}from"./time-B8FIgwPE.js";import{S as A}from"./select-DVYczzD1.js";import{C as $}from"./copy-CWx0Ec1c.js";import{T as D}from"./trash-2-DJgpvIoq.js";import"./utils-CDN07tui.js";/**
1
+ import{c as S,b as N,r as n,j as e,Z as E,D as T}from"./index-BHDlQcd6.js";import{P as C}from"./page-header-DhD-Kua9.js";import{a as y,f as w}from"./time-B8FIgwPE.js";import{S as A}from"./select-Cfv-njLE.js";import{C as $}from"./copy-BKOm9hko.js";import{T as D}from"./trash-2-BcQxmJP-.js";import"./utils-CDN07tui.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{i as f,r as s,u as w,k as v,j as e,e as g,m as j}from"./index-DbzQAv86.js";import{A as d}from"./auth-shell-NbHUThYR.js";import"./network-C5bJeATc.js";function k(i){return window.location.hash.replace(/^#/,"")||i||void 0}function S(){const i=f(),[r]=s.useState(()=>k(i.token)),u=w(),[a,o]=s.useState("checking"),[n,x]=s.useState(""),[c,h]=s.useState(""),[m,l]=s.useState(null);s.useEffect(()=>{(window.location.hash||i.token)&&window.history.replaceState(null,"","/xenon/reset-password")},[]),s.useEffect(()=>{if(!r){o("invalid");return}v(r).then(t=>o(t?"ready":"invalid"))},[r]);async function p(t){if(t.preventDefault(),l(null),n!==c){l("Passwords do not match.");return}if(n.length<8){l("Password must be at least 8 characters.");return}o("submitting");try{await j(r,n),o("done"),setTimeout(()=>u("/login",{replace:!0}),1500)}catch(b){l(b.message||"Reset failed"),o("ready")}}return a==="checking"?e.jsx(d,{children:e.jsx("div",{className:"text-sm text-[var(--text-muted)] text-center",children:"Checking link…"})}):a==="invalid"?e.jsx(d,{children:e.jsxs("div",{className:"text-center",children:[e.jsx("h1",{className:"text-xl font-semibold mb-3",children:"This link is invalid or expired"}),e.jsx("p",{className:"text-sm text-[var(--text-muted)] mb-6",children:"Reset links are good for 1 hour and single-use. Request a new one."}),e.jsx(g,{to:"/forgot-password",className:"inline-block h-10 px-4 rounded-md bg-[var(--color-accent)] text-[var(--color-on-accent)] text-sm font-medium leading-10",children:"Request a new link"})]})}):a==="done"?e.jsx(d,{children:e.jsxs("div",{className:"text-center",children:[e.jsx("h1",{className:"text-xl font-semibold mb-3",children:"Password updated"}),e.jsx("p",{className:"text-sm text-[var(--text-muted)]",children:"Redirecting to sign in…"})]})}):e.jsx(d,{children:e.jsxs("form",{onSubmit:p,children:[e.jsx("h1",{className:"text-2xl font-semibold mb-1",children:"Reset your password"}),e.jsx("p",{className:"text-sm text-[var(--text-muted)] mb-6",children:"Choose a new password to sign in with."}),e.jsx("label",{htmlFor:"reset-password",className:"block text-sm font-medium text-[var(--text)] mb-1.5",children:"New password"}),e.jsx("input",{id:"reset-password",type:"password",autoComplete:"new-password",value:n,onChange:t=>x(t.target.value),autoFocus:!0,required:!0,className:"w-full mb-3 h-10 px-3 rounded-lg bg-[var(--bg)] border border-[var(--border-strong)] text-sm text-[var(--text)] outline-none focus:border-[var(--color-accent)] focus:shadow-[0_0_0_3px_var(--accent-subtle)]"}),e.jsx("label",{htmlFor:"reset-confirm",className:"block text-sm font-medium text-[var(--text)] mb-1.5",children:"Confirm new password"}),e.jsx("input",{id:"reset-confirm",type:"password",autoComplete:"new-password",value:c,onChange:t=>h(t.target.value),required:!0,className:"w-full mb-3 h-10 px-3 rounded-lg bg-[var(--bg)] border border-[var(--border-strong)] text-sm text-[var(--text)] outline-none focus:border-[var(--color-accent)] focus:shadow-[0_0_0_3px_var(--accent-subtle)]"}),m&&e.jsx("div",{role:"alert",className:"text-sm text-[var(--red)] mb-3",children:m}),e.jsx("button",{type:"submit",disabled:a==="submitting"||n.length<8,className:"mt-5 w-full h-10 rounded-md bg-[var(--color-accent)] text-[var(--color-on-accent)] font-medium text-sm disabled:opacity-50",children:a==="submitting"?"Updating…":"Update password"})]})})}export{S as default};
1
+ import{i as f,r as s,u as w,k as v,j as e,e as g,m as j}from"./index-BHDlQcd6.js";import{A as d}from"./auth-shell-Vq1rYIVD.js";import"./network-B8fPwmW9.js";function k(i){return window.location.hash.replace(/^#/,"")||i||void 0}function S(){const i=f(),[r]=s.useState(()=>k(i.token)),u=w(),[a,o]=s.useState("checking"),[n,x]=s.useState(""),[c,h]=s.useState(""),[m,l]=s.useState(null);s.useEffect(()=>{(window.location.hash||i.token)&&window.history.replaceState(null,"","/xenon/reset-password")},[]),s.useEffect(()=>{if(!r){o("invalid");return}v(r).then(t=>o(t?"ready":"invalid"))},[r]);async function p(t){if(t.preventDefault(),l(null),n!==c){l("Passwords do not match.");return}if(n.length<8){l("Password must be at least 8 characters.");return}o("submitting");try{await j(r,n),o("done"),setTimeout(()=>u("/login",{replace:!0}),1500)}catch(b){l(b.message||"Reset failed"),o("ready")}}return a==="checking"?e.jsx(d,{children:e.jsx("div",{className:"text-sm text-[var(--text-muted)] text-center",children:"Checking link…"})}):a==="invalid"?e.jsx(d,{children:e.jsxs("div",{className:"text-center",children:[e.jsx("h1",{className:"text-xl font-semibold mb-3",children:"This link is invalid or expired"}),e.jsx("p",{className:"text-sm text-[var(--text-muted)] mb-6",children:"Reset links are good for 1 hour and single-use. Request a new one."}),e.jsx(g,{to:"/forgot-password",className:"inline-block h-10 px-4 rounded-md bg-[var(--color-accent)] text-[var(--color-on-accent)] text-sm font-medium leading-10",children:"Request a new link"})]})}):a==="done"?e.jsx(d,{children:e.jsxs("div",{className:"text-center",children:[e.jsx("h1",{className:"text-xl font-semibold mb-3",children:"Password updated"}),e.jsx("p",{className:"text-sm text-[var(--text-muted)]",children:"Redirecting to sign in…"})]})}):e.jsx(d,{children:e.jsxs("form",{onSubmit:p,children:[e.jsx("h1",{className:"text-2xl font-semibold mb-1",children:"Reset your password"}),e.jsx("p",{className:"text-sm text-[var(--text-muted)] mb-6",children:"Choose a new password to sign in with."}),e.jsx("label",{htmlFor:"reset-password",className:"block text-sm font-medium text-[var(--text)] mb-1.5",children:"New password"}),e.jsx("input",{id:"reset-password",type:"password",autoComplete:"new-password",value:n,onChange:t=>x(t.target.value),autoFocus:!0,required:!0,className:"w-full mb-3 h-10 px-3 rounded-lg bg-[var(--bg)] border border-[var(--border-strong)] text-sm text-[var(--text)] outline-none focus:border-[var(--color-accent)] focus:shadow-[0_0_0_3px_var(--accent-subtle)]"}),e.jsx("label",{htmlFor:"reset-confirm",className:"block text-sm font-medium text-[var(--text)] mb-1.5",children:"Confirm new password"}),e.jsx("input",{id:"reset-confirm",type:"password",autoComplete:"new-password",value:c,onChange:t=>h(t.target.value),required:!0,className:"w-full mb-3 h-10 px-3 rounded-lg bg-[var(--bg)] border border-[var(--border-strong)] text-sm text-[var(--text)] outline-none focus:border-[var(--color-accent)] focus:shadow-[0_0_0_3px_var(--accent-subtle)]"}),m&&e.jsx("div",{role:"alert",className:"text-sm text-[var(--red)] mb-3",children:m}),e.jsx("button",{type:"submit",disabled:a==="submitting"||n.length<8,className:"mt-5 w-full h-10 rounded-md bg-[var(--color-accent)] text-[var(--color-on-accent)] font-medium text-sm disabled:opacity-50",children:a==="submitting"?"Updating…":"Update password"})]})})}export{S as default};
@@ -1,4 +1,4 @@
1
- import{E as Me,w as Ie,i as Ir,u as Tr,j as J,F as Or}from"./index-DbzQAv86.js";import{A as vr}from"./arrow-left-QDqb-yc1.js";const qn=["http","https","mailto","tel"];function Lr(e){const n=(e||"").trim(),t=n.charAt(0);if(t==="#"||t==="/")return n;const r=n.indexOf(":");if(r===-1)return n;let i=-1;for(;++i<qn.length;){const l=qn[i];if(r===l.length&&n.slice(0,l.length).toLowerCase()===l)return n}return i=n.indexOf("?"),i!==-1&&r>i||(i=n.indexOf("#"),i!==-1&&r>i)?n:"javascript:void(0)"}/*!
1
+ import{E as Me,w as Ie,i as Ir,u as Tr,j as J,F as Or}from"./index-BHDlQcd6.js";import{A as vr}from"./arrow-left-CaOe18yC.js";const qn=["http","https","mailto","tel"];function Lr(e){const n=(e||"").trim(),t=n.charAt(0);if(t==="#"||t==="/")return n;const r=n.indexOf(":");if(r===-1)return n;let i=-1;for(;++i<qn.length;){const l=qn[i];if(r===l.length&&n.slice(0,l.length).toLowerCase()===l)return n}return i=n.indexOf("?"),i!==-1&&r>i||(i=n.indexOf("#"),i!==-1&&r>i)?n:"javascript:void(0)"}/*!
2
2
  * Determine if an object is a Buffer
3
3
  *
4
4
  * @author Feross Aboukhadijeh <https://feross.org>
@@ -1 +1 @@
1
- import{r as c,j as m}from"./index-DbzQAv86.js";import{c as o}from"./utils-CDN07tui.js";const l=c.forwardRef(({className:s,selectSize:e="md",children:t,...a},r)=>m.jsx("select",{ref:r,className:o("select-base",e==="sm"&&"select-sm",s),...a,children:t}));l.displayName="Select";export{l as S};
1
+ import{r as c,j as m}from"./index-BHDlQcd6.js";import{c as o}from"./utils-CDN07tui.js";const l=c.forwardRef(({className:s,selectSize:e="md",children:t,...a},r)=>m.jsx("select",{ref:r,className:o("select-base",e==="sm"&&"select-sm",s),...a,children:t}));l.displayName="Select";export{l as S};
@@ -1 +1 @@
1
- import{u as I,x as A,W as F,r,X as v,j as e,J as f,D as w,C as R,A as D}from"./index-DbzQAv86.js";import{f as g}from"./time-B8FIgwPE.js";import{P as S}from"./page-header-CqaoOYPH.js";import{f as P,H as B,V as H,E as L}from"./selector-health-DhzDZLlF.js";import{C as M,L as E}from"./layers-DprXM0fz.js";import{C as U}from"./circle-check-Dkg-Ka4_.js";import{Z as O}from"./zap-BLSqrVs9.js";import{C as q}from"./check-C8Un1cwf.js";import{C as K}from"./copy-CWx0Ec1c.js";function V(d){const h=Date.parse(d);if(!Number.isFinite(h))return"";const x=Math.max(0,Date.now()-h),l=Math.floor(x/1e3);if(l<60)return`${l}s ago`;const i=Math.floor(l/60);if(i<60)return`${i}m ago`;const o=Math.floor(i/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}function X(d){return d===0?"$0":d<.01?"<$0.01":`$${d.toFixed(2)}`}const te=()=>{const d=I(),{toast:h}=A(),[x]=F(),l=x.get("value")??"",i=x.get("strategy")??"",o=parseInt(x.get("windowDays")??"30",10)||30,[t,y]=r.useState(null),[a,p]=r.useState(null),[_,C]=r.useState(!1),[$,T]=r.useState(!1),[N,k]=r.useState(null),b=r.useCallback(async()=>{if(l){C(!0);try{const s=await v.getHealingSelectorDetail(l,o);y(s??null)}catch{y(null)}finally{C(!1),T(!0)}}},[l,o]),j=r.useCallback(async()=>{if(!i||!l){p(null);return}try{const s=await v.getSelectorState(i,l);p((s==null?void 0:s.state)??null)}catch{p(null)}},[i,l]);r.useEffect(()=>{b()},[b]),r.useEffect(()=>{j()},[j]);const m=r.useCallback(async s=>{if(!i||!l)return;const n=(()=>{switch(s){case"mark_fixed":return"Mark this selector as fixed? Move to Pending Verification.";case"mute":return"Mute this selector? It will be hidden from Hotspots, the CI gate, and digests.";case"unmute":return"Unmute this selector?";case"cancel_verification":return"Cancel verification? The selector returns to Active and the clean-build counter resets."}})();if(window.confirm(n))try{await v.postSelectorStateAction({original_strategy:i,original_selector:l,action:s}),h("Done.","success"),j()}catch(c){const u=c!=null&&c.currentStatus?` (currently ${c.currentStatus})`:"";h(`${(c==null?void 0:c.message)??"Action failed"}${u}`,"error")}},[i,l,h,j]),z=(s,n)=>{navigator.clipboard.writeText(s).catch(()=>{}),k(n),setTimeout(()=>k(c=>c===n?null:c),1500)};return l?e.jsxs("div",{className:"selector-health-page",children:[e.jsx(S,{icon:f,eyebrow:"Test quality / Selector",title:e.jsx("span",{className:"sh-detail-title",children:e.jsx("code",{title:l,children:l})}),subtitle:`All heals for this selector over the last ${o} days.`,action:e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"sh-back-btn",onClick:()=>d("/selector-health"),children:[e.jsx(M,{size:12})," Back"]}),e.jsxs("button",{className:"sh-refresh-btn",onClick:b,disabled:_,title:"Refresh",children:[e.jsx(w,{size:12,className:_?"animate-spin":""}),"Refresh"]})]})}),e.jsxs("div",{className:"sh-content",children:[i&&e.jsxs("div",{className:"sh-detail-strategy-line",title:"Tuple identity for this drill-down",children:[e.jsx("span",{className:"sh-strategy-badge",children:P(i)}),e.jsx("code",{children:l})]}),!i&&e.jsxs("div",{className:"sh-detail-banner",children:["Showing every strategy that ever healed this selector value. To scope to a single tuple, append ",e.jsx("code",{children:"?strategy=<name>"})," ","to the URL or open this row from the Active tab on Selector Health."]}),a&&e.jsxs("div",{className:`sh-detail-state sh-detail-state--${a.status}`,children:[e.jsxs("div",{className:"sh-detail-state__head",children:[e.jsxs("span",{className:"sh-detail-state__badge",children:[a.status==="pending"&&e.jsxs(e.Fragment,{children:[e.jsx(B,{size:16,style:{color:"var(--color-highlight)"}}),e.jsx("span",{children:"Pending verification"})]}),a.status==="resolved"&&e.jsxs(e.Fragment,{children:[e.jsx(U,{size:16,style:{color:"var(--color-success)"}}),e.jsx("span",{children:"Resolved"})]}),a.status==="muted"&&e.jsxs(e.Fragment,{children:[e.jsx(H,{size:16,style:{color:"var(--text-muted)"}}),e.jsx("span",{children:"Muted"})]}),a.status==="active"&&(a.regression_count>0?e.jsxs(e.Fragment,{children:[e.jsx(R,{size:16,style:{color:"var(--amber)"}}),e.jsxs("span",{children:["Active — regressed ",a.regression_count,"×"]})]}):e.jsxs(e.Fragment,{children:[e.jsx(O,{size:16}),e.jsx("span",{children:"Active"})]}))]}),e.jsxs("div",{className:"sh-detail-state__actions",children:[a.status==="active"&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:"sh-action-btn sh-action-btn--primary",onClick:()=>m("mark_fixed"),children:"Mark fixed"}),e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("mute"),children:"Mute"})]}),a.status==="pending"&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("cancel_verification"),children:"Cancel verification"}),e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("mute"),children:"Mute"})]}),a.status==="resolved"&&e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("mute"),children:"Mute"}),a.status==="muted"&&e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("unmute"),children:"Unmute"})]})]}),e.jsxs("div",{className:"sh-detail-state__meta",children:[a.fixed_at&&e.jsxs("span",{children:["Marked fixed: ",g(a.fixed_at)]}),a.resolved_at&&e.jsxs("span",{children:["Resolved: ",g(a.resolved_at)]}),a.muted_at&&e.jsxs("span",{children:["Muted: ",g(a.muted_at)]}),a.status==="pending"&&e.jsxs("span",{children:["Clean builds: ",a.clean_builds_count,"/3"]}),a.regression_count>0&&e.jsxs("span",{children:["Regressions on record: ",a.regression_count]})]})]}),_&&!$?e.jsxs("div",{className:"sh-empty",children:[e.jsx(w,{size:18,className:"animate-spin"}),e.jsx("span",{children:"Loading detail…"})]}):!t||t.healCount===0?e.jsxs("div",{className:"sh-empty sh-empty--clean",children:[e.jsx(f,{size:28}),e.jsxs("div",{children:[e.jsx("div",{className:"sh-empty__title",children:"No heals recorded"}),e.jsxs("div",{className:"sh-empty__subtitle",children:["This selector has no healing events in the last ",o," days."]})]})]}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"sh-detail-stats",children:[e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Heals"}),e.jsx("span",{className:"sh-detail-stat__value",children:t.healCount})]}),e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Sessions"}),e.jsx("span",{className:"sh-detail-stat__value",children:t.sessionCount})]}),e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Est. cost"}),e.jsx("span",{className:"sh-detail-stat__value",children:X(t.estCostUsd)})]}),e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Tiers used"}),e.jsx("span",{className:"sh-detail-stat__value",children:Object.keys(t.byTier).length||"—"})]})]}),e.jsxs("div",{className:"sh-detail-grid",children:[e.jsxs("section",{className:"sh-card",children:[e.jsxs("header",{className:"sh-card__head",children:[e.jsx(E,{size:12}),e.jsx("span",{children:"Suggested rewrites"}),e.jsx("span",{className:"sh-card__hint",children:"every alternate the healer landed on, ranked by frequency"})]}),e.jsxs("ul",{className:"sh-alternates",children:[t.alternates.length===0&&e.jsx("li",{className:"sh-alternates__empty",children:"No healed selectors recorded — only the failure was captured."}),t.alternates.map((s,n)=>{const c=Math.round(s.share*100),u=`alt-${n}`;return e.jsxs("li",{className:"sh-alternate",children:[e.jsxs("div",{className:"sh-alternate__top",children:[e.jsxs("span",{className:"sh-alternate__share",children:[c,"%"]}),e.jsx("code",{className:"sh-alternate__value",title:s.healedSelector,children:s.healedSelector}),e.jsx("button",{type:"button",className:`sh-icon-btn ${N===u?"copied":""}`,onClick:()=>z(s.healedSelector,u),"aria-label":"Copy alternate",title:N===u?"Copied":"Copy",children:N===u?e.jsx(q,{size:11}):e.jsx(K,{size:11})})]}),e.jsxs("div",{className:"sh-alternate__meta",children:[e.jsxs("span",{children:[s.count," heal",s.count===1?"":"s"]}),s.averageConfidence!==null&&e.jsxs("span",{children:["· avg ",Math.round(s.averageConfidence*100),"% confidence"]}),s.tiers.length>0&&e.jsxs("span",{children:["· via ",s.tiers.join(", ")]})]})]},s.healedSelector)})]})]}),e.jsxs("section",{className:"sh-card",children:[e.jsxs("header",{className:"sh-card__head",children:[e.jsx(D,{size:12}),e.jsx("span",{children:"Breakdown"})]}),e.jsxs("div",{className:"sh-breakdown",children:[e.jsxs("div",{className:"sh-breakdown__group",children:[e.jsx("div",{className:"sh-breakdown__label",children:"By tier"}),Object.entries(t.byTier).sort((s,n)=>n[1]-s[1]).map(([s,n])=>e.jsxs("div",{className:"sh-breakdown__row",children:[e.jsx("span",{className:"sh-tier-pill",children:s}),e.jsx("span",{className:"sh-breakdown__count",children:n})]},s)),Object.keys(t.byTier).length===0&&e.jsx("div",{className:"sh-breakdown__empty",children:"none recorded"})]}),e.jsxs("div",{className:"sh-breakdown__group",children:[e.jsx("div",{className:"sh-breakdown__label",children:"By platform"}),Object.entries(t.byPlatform).sort((s,n)=>n[1]-s[1]).map(([s,n])=>e.jsxs("div",{className:"sh-breakdown__row",children:[e.jsx("span",{className:"sh-platform-pill",children:s}),e.jsx("span",{className:"sh-breakdown__count",children:n})]},s))]}),e.jsxs("div",{className:"sh-breakdown__group",children:[e.jsx("div",{className:"sh-breakdown__label",children:"By build"}),t.byBuild.slice(0,8).map(s=>e.jsxs("div",{className:"sh-breakdown__row",children:[e.jsx("span",{className:"sh-build-pill",title:s.buildId,children:s.buildId==="unattached"?"unattached":s.buildId.length>12?s.buildId.slice(0,8)+"…":s.buildId}),e.jsx("span",{className:"sh-breakdown__count",children:s.count})]},s.buildId)),t.byBuild.length===0&&e.jsx("div",{className:"sh-breakdown__empty",children:"none recorded"})]})]})]})]}),e.jsxs("section",{className:"sh-card",children:[e.jsxs("header",{className:"sh-card__head",children:[e.jsx("span",{children:"Timeline"}),e.jsxs("span",{className:"sh-card__hint",children:["most recent ",t.timeline.length," of ",t.healCount," heal",t.healCount===1?"":"s"]})]}),e.jsx("div",{className:"sh-timeline",children:t.timeline.map(s=>e.jsxs("div",{className:"sh-timeline__row",children:[e.jsx("span",{className:"sh-timeline__when",children:V(s.createdAt)}),s.tier&&e.jsx("span",{className:"sh-tier-pill",children:s.tier}),e.jsx("span",{className:"sh-timeline__cmd",children:s.commandName??"—"}),e.jsx("span",{className:"sh-timeline__device",children:s.deviceName??s.deviceUdid??"—"}),e.jsx("code",{className:"sh-timeline__healed",title:s.healedSelector??"",children:s.healedSelector??"—"}),s.confidence!==null&&e.jsxs("span",{className:"sh-timeline__conf",children:[Math.round(s.confidence*100),"%"]}),s.buildId&&s.sessionId&&e.jsx("a",{className:"sh-timeline__link",href:`/xenon/builds/${encodeURIComponent(s.buildId)}/sessions/${encodeURIComponent(s.sessionId)}`,title:"Open session detail",children:e.jsx(L,{size:11})})]},s.id))})]})]})]})]}):e.jsxs("div",{className:"selector-health-page",children:[e.jsx(S,{icon:f,eyebrow:"Test quality",title:"Selector detail",subtitle:"No selector specified."}),e.jsx("div",{className:"sh-content",children:e.jsxs("button",{className:"sh-back-btn",onClick:()=>d("/selector-health"),children:[e.jsx(M,{size:12})," Back to Selector health"]})})]})};export{te as default};
1
+ import{u as I,x as A,W as F,r,X as v,j as e,J as f,D as w,C as R,A as D}from"./index-BHDlQcd6.js";import{f as g}from"./time-B8FIgwPE.js";import{P as S}from"./page-header-DhD-Kua9.js";import{f as P,H as B,V as H,E as L}from"./selector-health-BP45B44s.js";import{C as M,L as E}from"./layers-CLuKN5Oy.js";import{C as U}from"./circle-check-Cokzy9a2.js";import{Z as O}from"./zap-CqGOjAjn.js";import{C as q}from"./check-DYccNk73.js";import{C as K}from"./copy-BKOm9hko.js";function V(d){const h=Date.parse(d);if(!Number.isFinite(h))return"";const x=Math.max(0,Date.now()-h),l=Math.floor(x/1e3);if(l<60)return`${l}s ago`;const i=Math.floor(l/60);if(i<60)return`${i}m ago`;const o=Math.floor(i/60);return o<24?`${o}h ago`:`${Math.floor(o/24)}d ago`}function X(d){return d===0?"$0":d<.01?"<$0.01":`$${d.toFixed(2)}`}const te=()=>{const d=I(),{toast:h}=A(),[x]=F(),l=x.get("value")??"",i=x.get("strategy")??"",o=parseInt(x.get("windowDays")??"30",10)||30,[t,y]=r.useState(null),[a,p]=r.useState(null),[_,C]=r.useState(!1),[$,T]=r.useState(!1),[N,k]=r.useState(null),b=r.useCallback(async()=>{if(l){C(!0);try{const s=await v.getHealingSelectorDetail(l,o);y(s??null)}catch{y(null)}finally{C(!1),T(!0)}}},[l,o]),j=r.useCallback(async()=>{if(!i||!l){p(null);return}try{const s=await v.getSelectorState(i,l);p((s==null?void 0:s.state)??null)}catch{p(null)}},[i,l]);r.useEffect(()=>{b()},[b]),r.useEffect(()=>{j()},[j]);const m=r.useCallback(async s=>{if(!i||!l)return;const n=(()=>{switch(s){case"mark_fixed":return"Mark this selector as fixed? Move to Pending Verification.";case"mute":return"Mute this selector? It will be hidden from Hotspots, the CI gate, and digests.";case"unmute":return"Unmute this selector?";case"cancel_verification":return"Cancel verification? The selector returns to Active and the clean-build counter resets."}})();if(window.confirm(n))try{await v.postSelectorStateAction({original_strategy:i,original_selector:l,action:s}),h("Done.","success"),j()}catch(c){const u=c!=null&&c.currentStatus?` (currently ${c.currentStatus})`:"";h(`${(c==null?void 0:c.message)??"Action failed"}${u}`,"error")}},[i,l,h,j]),z=(s,n)=>{navigator.clipboard.writeText(s).catch(()=>{}),k(n),setTimeout(()=>k(c=>c===n?null:c),1500)};return l?e.jsxs("div",{className:"selector-health-page",children:[e.jsx(S,{icon:f,eyebrow:"Test quality / Selector",title:e.jsx("span",{className:"sh-detail-title",children:e.jsx("code",{title:l,children:l})}),subtitle:`All heals for this selector over the last ${o} days.`,action:e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"sh-back-btn",onClick:()=>d("/selector-health"),children:[e.jsx(M,{size:12})," Back"]}),e.jsxs("button",{className:"sh-refresh-btn",onClick:b,disabled:_,title:"Refresh",children:[e.jsx(w,{size:12,className:_?"animate-spin":""}),"Refresh"]})]})}),e.jsxs("div",{className:"sh-content",children:[i&&e.jsxs("div",{className:"sh-detail-strategy-line",title:"Tuple identity for this drill-down",children:[e.jsx("span",{className:"sh-strategy-badge",children:P(i)}),e.jsx("code",{children:l})]}),!i&&e.jsxs("div",{className:"sh-detail-banner",children:["Showing every strategy that ever healed this selector value. To scope to a single tuple, append ",e.jsx("code",{children:"?strategy=<name>"})," ","to the URL or open this row from the Active tab on Selector Health."]}),a&&e.jsxs("div",{className:`sh-detail-state sh-detail-state--${a.status}`,children:[e.jsxs("div",{className:"sh-detail-state__head",children:[e.jsxs("span",{className:"sh-detail-state__badge",children:[a.status==="pending"&&e.jsxs(e.Fragment,{children:[e.jsx(B,{size:16,style:{color:"var(--color-highlight)"}}),e.jsx("span",{children:"Pending verification"})]}),a.status==="resolved"&&e.jsxs(e.Fragment,{children:[e.jsx(U,{size:16,style:{color:"var(--color-success)"}}),e.jsx("span",{children:"Resolved"})]}),a.status==="muted"&&e.jsxs(e.Fragment,{children:[e.jsx(H,{size:16,style:{color:"var(--text-muted)"}}),e.jsx("span",{children:"Muted"})]}),a.status==="active"&&(a.regression_count>0?e.jsxs(e.Fragment,{children:[e.jsx(R,{size:16,style:{color:"var(--amber)"}}),e.jsxs("span",{children:["Active — regressed ",a.regression_count,"×"]})]}):e.jsxs(e.Fragment,{children:[e.jsx(O,{size:16}),e.jsx("span",{children:"Active"})]}))]}),e.jsxs("div",{className:"sh-detail-state__actions",children:[a.status==="active"&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:"sh-action-btn sh-action-btn--primary",onClick:()=>m("mark_fixed"),children:"Mark fixed"}),e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("mute"),children:"Mute"})]}),a.status==="pending"&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("cancel_verification"),children:"Cancel verification"}),e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("mute"),children:"Mute"})]}),a.status==="resolved"&&e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("mute"),children:"Mute"}),a.status==="muted"&&e.jsx("button",{type:"button",className:"sh-action-btn",onClick:()=>m("unmute"),children:"Unmute"})]})]}),e.jsxs("div",{className:"sh-detail-state__meta",children:[a.fixed_at&&e.jsxs("span",{children:["Marked fixed: ",g(a.fixed_at)]}),a.resolved_at&&e.jsxs("span",{children:["Resolved: ",g(a.resolved_at)]}),a.muted_at&&e.jsxs("span",{children:["Muted: ",g(a.muted_at)]}),a.status==="pending"&&e.jsxs("span",{children:["Clean builds: ",a.clean_builds_count,"/3"]}),a.regression_count>0&&e.jsxs("span",{children:["Regressions on record: ",a.regression_count]})]})]}),_&&!$?e.jsxs("div",{className:"sh-empty",children:[e.jsx(w,{size:18,className:"animate-spin"}),e.jsx("span",{children:"Loading detail…"})]}):!t||t.healCount===0?e.jsxs("div",{className:"sh-empty sh-empty--clean",children:[e.jsx(f,{size:28}),e.jsxs("div",{children:[e.jsx("div",{className:"sh-empty__title",children:"No heals recorded"}),e.jsxs("div",{className:"sh-empty__subtitle",children:["This selector has no healing events in the last ",o," days."]})]})]}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"sh-detail-stats",children:[e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Heals"}),e.jsx("span",{className:"sh-detail-stat__value",children:t.healCount})]}),e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Sessions"}),e.jsx("span",{className:"sh-detail-stat__value",children:t.sessionCount})]}),e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Est. cost"}),e.jsx("span",{className:"sh-detail-stat__value",children:X(t.estCostUsd)})]}),e.jsxs("div",{className:"sh-detail-stat",children:[e.jsx("span",{className:"sh-detail-stat__label",children:"Tiers used"}),e.jsx("span",{className:"sh-detail-stat__value",children:Object.keys(t.byTier).length||"—"})]})]}),e.jsxs("div",{className:"sh-detail-grid",children:[e.jsxs("section",{className:"sh-card",children:[e.jsxs("header",{className:"sh-card__head",children:[e.jsx(E,{size:12}),e.jsx("span",{children:"Suggested rewrites"}),e.jsx("span",{className:"sh-card__hint",children:"every alternate the healer landed on, ranked by frequency"})]}),e.jsxs("ul",{className:"sh-alternates",children:[t.alternates.length===0&&e.jsx("li",{className:"sh-alternates__empty",children:"No healed selectors recorded — only the failure was captured."}),t.alternates.map((s,n)=>{const c=Math.round(s.share*100),u=`alt-${n}`;return e.jsxs("li",{className:"sh-alternate",children:[e.jsxs("div",{className:"sh-alternate__top",children:[e.jsxs("span",{className:"sh-alternate__share",children:[c,"%"]}),e.jsx("code",{className:"sh-alternate__value",title:s.healedSelector,children:s.healedSelector}),e.jsx("button",{type:"button",className:`sh-icon-btn ${N===u?"copied":""}`,onClick:()=>z(s.healedSelector,u),"aria-label":"Copy alternate",title:N===u?"Copied":"Copy",children:N===u?e.jsx(q,{size:11}):e.jsx(K,{size:11})})]}),e.jsxs("div",{className:"sh-alternate__meta",children:[e.jsxs("span",{children:[s.count," heal",s.count===1?"":"s"]}),s.averageConfidence!==null&&e.jsxs("span",{children:["· avg ",Math.round(s.averageConfidence*100),"% confidence"]}),s.tiers.length>0&&e.jsxs("span",{children:["· via ",s.tiers.join(", ")]})]})]},s.healedSelector)})]})]}),e.jsxs("section",{className:"sh-card",children:[e.jsxs("header",{className:"sh-card__head",children:[e.jsx(D,{size:12}),e.jsx("span",{children:"Breakdown"})]}),e.jsxs("div",{className:"sh-breakdown",children:[e.jsxs("div",{className:"sh-breakdown__group",children:[e.jsx("div",{className:"sh-breakdown__label",children:"By tier"}),Object.entries(t.byTier).sort((s,n)=>n[1]-s[1]).map(([s,n])=>e.jsxs("div",{className:"sh-breakdown__row",children:[e.jsx("span",{className:"sh-tier-pill",children:s}),e.jsx("span",{className:"sh-breakdown__count",children:n})]},s)),Object.keys(t.byTier).length===0&&e.jsx("div",{className:"sh-breakdown__empty",children:"none recorded"})]}),e.jsxs("div",{className:"sh-breakdown__group",children:[e.jsx("div",{className:"sh-breakdown__label",children:"By platform"}),Object.entries(t.byPlatform).sort((s,n)=>n[1]-s[1]).map(([s,n])=>e.jsxs("div",{className:"sh-breakdown__row",children:[e.jsx("span",{className:"sh-platform-pill",children:s}),e.jsx("span",{className:"sh-breakdown__count",children:n})]},s))]}),e.jsxs("div",{className:"sh-breakdown__group",children:[e.jsx("div",{className:"sh-breakdown__label",children:"By build"}),t.byBuild.slice(0,8).map(s=>e.jsxs("div",{className:"sh-breakdown__row",children:[e.jsx("span",{className:"sh-build-pill",title:s.buildId,children:s.buildId==="unattached"?"unattached":s.buildId.length>12?s.buildId.slice(0,8)+"…":s.buildId}),e.jsx("span",{className:"sh-breakdown__count",children:s.count})]},s.buildId)),t.byBuild.length===0&&e.jsx("div",{className:"sh-breakdown__empty",children:"none recorded"})]})]})]})]}),e.jsxs("section",{className:"sh-card",children:[e.jsxs("header",{className:"sh-card__head",children:[e.jsx("span",{children:"Timeline"}),e.jsxs("span",{className:"sh-card__hint",children:["most recent ",t.timeline.length," of ",t.healCount," heal",t.healCount===1?"":"s"]})]}),e.jsx("div",{className:"sh-timeline",children:t.timeline.map(s=>e.jsxs("div",{className:"sh-timeline__row",children:[e.jsx("span",{className:"sh-timeline__when",children:V(s.createdAt)}),s.tier&&e.jsx("span",{className:"sh-tier-pill",children:s.tier}),e.jsx("span",{className:"sh-timeline__cmd",children:s.commandName??"—"}),e.jsx("span",{className:"sh-timeline__device",children:s.deviceName??s.deviceUdid??"—"}),e.jsx("code",{className:"sh-timeline__healed",title:s.healedSelector??"",children:s.healedSelector??"—"}),s.confidence!==null&&e.jsxs("span",{className:"sh-timeline__conf",children:[Math.round(s.confidence*100),"%"]}),s.buildId&&s.sessionId&&e.jsx("a",{className:"sh-timeline__link",href:`/xenon/builds/${encodeURIComponent(s.buildId)}/sessions/${encodeURIComponent(s.sessionId)}`,title:"Open session detail",children:e.jsx(L,{size:11})})]},s.id))})]})]})]})]}):e.jsxs("div",{className:"selector-health-page",children:[e.jsx(S,{icon:f,eyebrow:"Test quality",title:"Selector detail",subtitle:"No selector specified."}),e.jsx("div",{className:"sh-content",children:e.jsxs("button",{className:"sh-back-btn",onClick:()=>d("/selector-health"),children:[e.jsx(M,{size:12})," Back to Selector health"]})})]})};export{te as default};
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-DbzQAv86.js";/**
1
+ import{c as e}from"./index-BHDlQcd6.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as z,u as H,W as Y,j as e,r as h,B as le,y as P,C as re,X as k,D as A,p as Z,x as oe,J as X,A as ce,I as de}from"./index-DbzQAv86.js";import{Z as he}from"./zap-BLSqrVs9.js";import{H as B,V as R,f as w,E as ue}from"./selector-health-DhzDZLlF.js";import{C as Q}from"./circle-check-Dkg-Ka4_.js";import{M as me}from"./Modal-LXKxgyV7.js";import{C as pe}from"./check-C8Un1cwf.js";import{C as ge}from"./copy-CWx0Ec1c.js";import{b as N}from"./time-B8FIgwPE.js";import{E as ve}from"./EmptyState-CTmGgoSm.js";import{P as xe}from"./page-header-CqaoOYPH.js";import{S as be}from"./SegmentedControl-BSWMM3qE.js";import{p as fe}from"./labels-C21SA4JG.js";import{F as J}from"./funnel-DR8YKoFW.js";import{C as _e}from"./chevron-right-DHjIgZvn.js";import{A as G}from"./arrow-up-right-DOwr8XxB.js";/**
1
+ import{c as z,u as H,W as Y,j as e,r as h,B as le,y as P,C as re,X as k,D as A,p as Z,x as oe,J as X,A as ce,I as de}from"./index-BHDlQcd6.js";import{Z as he}from"./zap-CqGOjAjn.js";import{H as B,V as R,f as w,E as ue}from"./selector-health-BP45B44s.js";import{C as Q}from"./circle-check-Cokzy9a2.js";import{M as me}from"./Modal-C5W8lMJ6.js";import{C as pe}from"./check-DYccNk73.js";import{C as ge}from"./copy-BKOm9hko.js";import{b as N}from"./time-B8FIgwPE.js";import{E as ve}from"./EmptyState-CQun3BJE.js";import{P as xe}from"./page-header-DhD-Kua9.js";import{S as be}from"./SegmentedControl-BIzlKyUd.js";import{p as fe}from"./labels-C21SA4JG.js";import{F as J}from"./funnel-SD4F5IdR.js";import{C as _e}from"./chevron-right-C6ny35i3.js";import{A as G}from"./arrow-up-right-Dnszb73S.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as Y,r as d,X as _,x as D,j as e,e as B,A as G,p as Q,i as Z,u as q}from"./index-DbzQAv86.js";import{u as ee,h as te,c as se,d as re,p as ae,o as ne,f as ie,S as oe}from"./status-pill-outline-BgQ_Ur6B.js";import{B as le,P as j}from"./BugReportButton-0m4q010L.js";import{A as ce}from"./arrow-left-QDqb-yc1.js";import{C as $}from"./chevron-right-DHjIgZvn.js";import{C as L}from"./copy-CWx0Ec1c.js";import{A as de}from"./arrow-up-right-DOwr8XxB.js";import{E as O}from"./EmptyState-CTmGgoSm.js";import{T as xe,a as me,b as M,c as S,d as ue,e as R}from"./Table-EHWJCCzN.js";import{M as fe}from"./Modal-LXKxgyV7.js";import{D as he}from"./download-DJ_pjhGv.js";import{N as pe}from"./network-C5bJeATc.js";import{s as ve}from"./labels-C21SA4JG.js";import"./time-B8FIgwPE.js";/**
1
+ import{c as Y,r as d,X as _,x as D,j as e,e as B,A as G,p as Q,i as Z,u as q}from"./index-BHDlQcd6.js";import{u as ee,h as te,c as se,d as re,p as ae,o as ne,f as ie,S as oe}from"./status-pill-outline-CLa0C1Sg.js";import{B as le,P as j}from"./BugReportButton-Sk0KGufH.js";import{A as ce}from"./arrow-left-CaOe18yC.js";import{C as $}from"./chevron-right-C6ny35i3.js";import{C as L}from"./copy-BKOm9hko.js";import{A as de}from"./arrow-up-right-Dnszb73S.js";import{E as O}from"./EmptyState-CQun3BJE.js";import{T as xe,a as me,b as M,c as S,d as ue,e as R}from"./Table-DRkkM4OR.js";import{M as fe}from"./Modal-C5W8lMJ6.js";import{D as he}from"./download-R2k7Nd4P.js";import{N as pe}from"./network-B8fPwmW9.js";import{s as ve}from"./labels-C21SA4JG.js";import"./time-B8FIgwPE.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{x as q,r,p as B,X as g,j as e,D as P,K as X,C as G,e as V,O,P as K}from"./index-DbzQAv86.js";/* empty css */import{S as j,A as U}from"./SettingCard-CeHOIGW9.js";import{M as W}from"./Modal-LXKxgyV7.js";import{B as E}from"./button-Do8mBvkU.js";import{P as J}from"./page-header-CqaoOYPH.js";import{C as I}from"./circle-check-Dkg-Ka4_.js";import{C as Q}from"./clock-CQLUBaRx.js";import{M as Y,F as Z}from"./mouse-pointer-2-BD4cWXDU.js";import{C as ee}from"./check-C8Un1cwf.js";import{A as se}from"./activity-CHfTPuBn.js";import{C as ae}from"./calendar-DrH0mjSD.js";import{A as te}from"./arrow-up-right-DOwr8XxB.js";import"./utils-CDN07tui.js";const m={healthCheckIntervalMs:3e4,healthCheckSchedule:"",enableSelfHealing:!0},S=5e3,ne=[{label:"Battery saver (2 AM)",value:"0 2 * * *"},{label:"Standard (hourly)",value:"0 * * * *"},{label:"Operational coverage (30m)",value:"*/30 * * * *"},{label:"High performance (10m)",value:"*/10 * * * *"}],N=5;function le(t){const a=Date.parse(t);if(!Number.isFinite(a))return"";const n=Math.max(0,Date.now()-a),i=Math.floor(n/1e3);if(i<60)return`${i}s ago`;const h=Math.floor(i/60);if(h<60)return`${h}m ago`;const d=Math.floor(h/60);return d<24?`${d}h ago`:`${Math.floor(d/24)}d ago`}function A(t){var d,u;const a=(d=t.originalSelector)==null?void 0:d.trim(),n=(u=t.healedSelector)==null?void 0:u.trim(),i=t.tier&&t.commandName?`${t.commandName}: `:"";if(a&&n&&a!==n)return`${i}${a} → ${n}`;if(n)return`${i}Recovered locator ${n}`;if(a)return`${i}Recovered locator ${a}`;const h=typeof t.confidence=="number"?` (${Math.round(t.confidence*100)}%)`:"";return`${i||""}Self-healed${h}`}function ie(t){return t.tier?t.tier:t.commandName?t.commandName:t.deviceName?t.deviceName:"Self-heal"}const re=(t,a)=>t.healthCheckIntervalMs===a.healthCheckIntervalMs&&t.healthCheckSchedule===a.healthCheckSchedule&&t.enableSelfHealing===a.enableSelfHealing,Ce=()=>{const{toast:t}=q(),[a,n]=r.useState(m),[i,h]=r.useState(m),[d,u]=r.useState(!0),[C,b]=r.useState(!1),[w,f]=r.useState(!1),[y,k]=r.useState([]),[$,H]=r.useState(!1),{on:M}=B();r.useEffect(()=>{z()},[]),r.useEffect(()=>{let s=!1;return g.getRecentHealingEvents(N).then(l=>{if(s||!l)return;const o=Array.isArray(l.events)?l.events.slice(0,N):[];k(o)}).catch(()=>{}).finally(()=>{s||H(!0)}),()=>{s=!0}},[]),r.useEffect(()=>{const s=M("healing_event",l=>{const o=l;!o||!o.id||k(p=>p.some(F=>F.id===o.id)?p:[o,...p].slice(0,N))});return()=>{s&&s()}},[M]);const z=async()=>{u(!0);try{const s=await g.getGlobalConfig(),l={healthCheckIntervalMs:s.healthCheckIntervalMs||3e4,healthCheckSchedule:s.healthCheckSchedule||"",enableSelfHealing:s.enableSelfHealing!==void 0?s.enableSelfHealing:!0};n(l),h(l)}catch(s){console.error("Failed to load settings",s),t("Failed to access infrastructure parameters.","error")}finally{u(!1)}},v=!re(a,i),c=Number.isNaN(a.healthCheckIntervalMs)||a.healthCheckIntervalMs<S?`Below minimum safe value of ${S}ms.`:null,R=v&&!c,x={interval:a.healthCheckIntervalMs!==i.healthCheckIntervalMs,schedule:a.healthCheckSchedule!==i.healthCheckSchedule,healing:a.enableSelfHealing!==i.enableSelfHealing},_=async s=>{const l=s??a;if(!s&&c){t(c,"error");return}b(!0);try{await g.updateGlobalConfig(l),h(l),n(l),t("Infrastructure parameters synchronized across fleet.","success")}catch(o){console.error("Failed to save settings",o),t(K(o),"error")}finally{b(!1)}},L=async()=>{n(m),await _(m);try{await g.resetMetrics()}catch(s){console.error("Failed to reset metrics",s)}},D=()=>{n(i)},T=a.healthCheckSchedule!=="";return d?e.jsxs("div",{className:"settings-loading",children:[e.jsx(P,{className:"animate-spin",size:32}),e.jsx("span",{children:"Synchronizing Global Infrastructure..."})]}):e.jsxs("div",{className:"settings-container",children:[e.jsx(J,{icon:X,title:"Settings",subtitle:"Manage core farm parameters, heartbeat frequency, and maintenance orchestrations across the global registry."}),e.jsx("div",{className:"settings-content",children:e.jsxs("div",{className:"settings-grid settings-grid--three",children:[e.jsxs(j,{icon:e.jsx(Q,{size:16}),title:e.jsxs("span",{className:"card-title-row",children:["Idle health frequency",x.interval&&e.jsx("span",{className:"modified-dot","aria-label":"Modified"})]}),description:"Frequency of passive health pings when the system is in idle state.",hint:"Minimum safe value: 5000ms. Note: This frequency is overridden when a schedule is active.",children:[e.jsx("div",{className:"card-spacer"}),e.jsxs("div",{className:`input-group ${c?"has-error":""}`,children:[e.jsx("input",{type:"number",value:a.healthCheckIntervalMs,onChange:s=>n({...a,healthCheckIntervalMs:parseInt(s.target.value,10)}),min:S,step:5e3,"aria-invalid":!!c,"aria-describedby":c?"interval-error":"interval-status"}),e.jsx("span",{className:"code-font",children:"MS"})]}),c?e.jsxs("div",{id:"interval-error",className:"input-status input-status--error",role:"alert",children:[e.jsx(G,{size:12}),e.jsx("span",{children:c})]}):e.jsxs("div",{id:"interval-status",className:"input-status input-status--ok",children:[e.jsx(I,{size:12}),e.jsx("span",{children:"Valid frequency"})]})]}),e.jsxs(j,{icon:e.jsx(ae,{size:16}),title:e.jsxs("span",{className:"card-title-row",children:["Deep diagnostic schedule",x.schedule&&e.jsx("span",{className:"modified-dot","aria-label":"Modified"})]}),titleExtra:T?e.jsxs("span",{className:"active-pill",children:[e.jsx(se,{size:11}),e.jsx("span",{children:"Active"})]}):null,description:"Execute intensive reliability bursts (WDA restarts, Cache purges) using standardized Cron syntax.",children:[e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{type:"text",placeholder:"e.g. 0 * * * * (hourly, at minute 0)",value:a.healthCheckSchedule,onChange:s=>n({...a,healthCheckSchedule:s.target.value})})}),e.jsxs("div",{className:"cron-presets",children:[e.jsxs("div",{className:"presets-label",children:[e.jsx(Y,{size:12}),e.jsx("span",{children:"Intent-based presets"})]}),e.jsx("div",{className:"presets-grid presets-grid--stacked",children:ne.map(s=>{const l=a.healthCheckSchedule===s.value;return e.jsxs("button",{type:"button",className:`preset-chip ${l?"active":""}`,onClick:()=>n({...a,healthCheckSchedule:s.value}),"aria-pressed":l,children:[l&&e.jsx(ee,{size:11,className:"preset-chip__check"}),e.jsx("span",{children:s.label})]},s.label)})})]})]}),e.jsxs(j,{icon:e.jsx(O,{size:16}),title:e.jsxs("span",{className:"card-title-row",children:["AI self-healing",x.healing&&e.jsx("span",{className:"modified-dot","aria-label":"Modified"})]}),titleExtra:e.jsxs("label",{className:"switch switch--header",children:[e.jsx("input",{type:"checkbox",checked:a.enableSelfHealing,onChange:s=>n({...a,enableSelfHealing:s.target.checked}),"aria-label":"Toggle AI self-healing"}),e.jsx("span",{className:"slider round"})]}),children:[e.jsx("div",{className:`healing-state ${a.enableSelfHealing?"is-on":"is-off"}`,children:a.enableSelfHealing?"Enabled":"Disabled"}),e.jsx("p",{className:"setting-card-description",children:"Automatically intercept and recover from failing locators using Xenon's 6-tier strategy: etalon recovery, native retry, fuzzy XML, OCR, visual AI, and LLM — before failing a test."}),a.enableSelfHealing&&e.jsxs("div",{className:"healing-events",children:[e.jsxs("div",{className:"healing-events__header",children:[e.jsx(Z,{size:11}),e.jsx("span",{children:"Recent healing events"}),e.jsxs(V,{to:"/selector-health",className:"healing-events__link",children:["Selector health ",e.jsx(te,{size:10})]})]}),y.length>0?e.jsx("ul",{className:"healing-events__list",children:y.map(s=>e.jsxs("li",{className:"healing-event",children:[e.jsx(I,{size:14,className:"healing-event__icon"}),e.jsxs("div",{className:"healing-event__body",children:[e.jsxs("div",{className:"healing-event__row",children:[e.jsx("span",{className:"healing-event__kind",children:ie(s)}),e.jsx("span",{className:"healing-event__when",children:le(s.createdAt)})]}),e.jsx("div",{className:"healing-event__message",title:A(s),children:A(s)})]})]},s.id))}):e.jsx("div",{className:"healing-events__empty",children:$?"No healing events recorded yet — the dashboard will populate as locators are recovered.":"Loading recent events…"})]})]})]})}),(v||C)&&e.jsx(U,{onSave:()=>_(),onDiscard:D,onRestoreDefaults:()=>f(!0),isSaving:C,isValidating:!!c,isDirty:v,saveLabel:R?"Save Configuration":"Resolve errors to save"}),e.jsxs(W,{open:w,onClose:()=>f(!1),title:"Restore default settings?",footer:e.jsxs(e.Fragment,{children:[e.jsx(E,{variant:"secondary",onClick:()=>f(!1),children:"Cancel"}),e.jsx(E,{variant:"danger",onClick:async()=>{f(!1),await L()},children:"Restore defaults"})]}),children:[e.jsx("p",{className:"text-sm text-[var(--text-muted)]",children:"This saves immediately and can't be undone:"}),e.jsxs("ul",{className:"mt-2 list-disc pl-5 text-sm text-[var(--text)] space-y-1",children:[e.jsxs("li",{children:["Idle health frequency goes back to ",m.healthCheckIntervalMs.toLocaleString()," ms"]}),e.jsx("li",{children:"The diagnostic schedule is cleared"}),e.jsx("li",{children:"AI self-healing is turned on"}),e.jsx("li",{children:"Every device's healed-selector count is reset to zero"})]})]})]})};export{Ce as Settings};
1
+ import{x as q,r,p as B,X as g,j as e,D as P,K as X,C as G,e as V,O,P as K}from"./index-BHDlQcd6.js";/* empty css */import{S as j,A as U}from"./SettingCard-DAFqQw7S.js";import{M as W}from"./Modal-C5W8lMJ6.js";import{B as E}from"./button-DrqhMLZ0.js";import{P as J}from"./page-header-DhD-Kua9.js";import{C as I}from"./circle-check-Cokzy9a2.js";import{C as Q}from"./clock-BCG0drfS.js";import{M as Y,F as Z}from"./mouse-pointer-2-C6yy1DTx.js";import{C as ee}from"./check-DYccNk73.js";import{A as se}from"./activity-B6x69oOy.js";import{C as ae}from"./calendar-BuRuUa7q.js";import{A as te}from"./arrow-up-right-Dnszb73S.js";import"./utils-CDN07tui.js";const m={healthCheckIntervalMs:3e4,healthCheckSchedule:"",enableSelfHealing:!0},S=5e3,ne=[{label:"Battery saver (2 AM)",value:"0 2 * * *"},{label:"Standard (hourly)",value:"0 * * * *"},{label:"Operational coverage (30m)",value:"*/30 * * * *"},{label:"High performance (10m)",value:"*/10 * * * *"}],N=5;function le(t){const a=Date.parse(t);if(!Number.isFinite(a))return"";const n=Math.max(0,Date.now()-a),i=Math.floor(n/1e3);if(i<60)return`${i}s ago`;const h=Math.floor(i/60);if(h<60)return`${h}m ago`;const d=Math.floor(h/60);return d<24?`${d}h ago`:`${Math.floor(d/24)}d ago`}function A(t){var d,u;const a=(d=t.originalSelector)==null?void 0:d.trim(),n=(u=t.healedSelector)==null?void 0:u.trim(),i=t.tier&&t.commandName?`${t.commandName}: `:"";if(a&&n&&a!==n)return`${i}${a} → ${n}`;if(n)return`${i}Recovered locator ${n}`;if(a)return`${i}Recovered locator ${a}`;const h=typeof t.confidence=="number"?` (${Math.round(t.confidence*100)}%)`:"";return`${i||""}Self-healed${h}`}function ie(t){return t.tier?t.tier:t.commandName?t.commandName:t.deviceName?t.deviceName:"Self-heal"}const re=(t,a)=>t.healthCheckIntervalMs===a.healthCheckIntervalMs&&t.healthCheckSchedule===a.healthCheckSchedule&&t.enableSelfHealing===a.enableSelfHealing,Ce=()=>{const{toast:t}=q(),[a,n]=r.useState(m),[i,h]=r.useState(m),[d,u]=r.useState(!0),[C,b]=r.useState(!1),[w,f]=r.useState(!1),[y,k]=r.useState([]),[$,H]=r.useState(!1),{on:M}=B();r.useEffect(()=>{z()},[]),r.useEffect(()=>{let s=!1;return g.getRecentHealingEvents(N).then(l=>{if(s||!l)return;const o=Array.isArray(l.events)?l.events.slice(0,N):[];k(o)}).catch(()=>{}).finally(()=>{s||H(!0)}),()=>{s=!0}},[]),r.useEffect(()=>{const s=M("healing_event",l=>{const o=l;!o||!o.id||k(p=>p.some(F=>F.id===o.id)?p:[o,...p].slice(0,N))});return()=>{s&&s()}},[M]);const z=async()=>{u(!0);try{const s=await g.getGlobalConfig(),l={healthCheckIntervalMs:s.healthCheckIntervalMs||3e4,healthCheckSchedule:s.healthCheckSchedule||"",enableSelfHealing:s.enableSelfHealing!==void 0?s.enableSelfHealing:!0};n(l),h(l)}catch(s){console.error("Failed to load settings",s),t("Failed to access infrastructure parameters.","error")}finally{u(!1)}},v=!re(a,i),c=Number.isNaN(a.healthCheckIntervalMs)||a.healthCheckIntervalMs<S?`Below minimum safe value of ${S}ms.`:null,R=v&&!c,x={interval:a.healthCheckIntervalMs!==i.healthCheckIntervalMs,schedule:a.healthCheckSchedule!==i.healthCheckSchedule,healing:a.enableSelfHealing!==i.enableSelfHealing},_=async s=>{const l=s??a;if(!s&&c){t(c,"error");return}b(!0);try{await g.updateGlobalConfig(l),h(l),n(l),t("Infrastructure parameters synchronized across fleet.","success")}catch(o){console.error("Failed to save settings",o),t(K(o),"error")}finally{b(!1)}},L=async()=>{n(m),await _(m);try{await g.resetMetrics()}catch(s){console.error("Failed to reset metrics",s)}},D=()=>{n(i)},T=a.healthCheckSchedule!=="";return d?e.jsxs("div",{className:"settings-loading",children:[e.jsx(P,{className:"animate-spin",size:32}),e.jsx("span",{children:"Synchronizing Global Infrastructure..."})]}):e.jsxs("div",{className:"settings-container",children:[e.jsx(J,{icon:X,title:"Settings",subtitle:"Manage core farm parameters, heartbeat frequency, and maintenance orchestrations across the global registry."}),e.jsx("div",{className:"settings-content",children:e.jsxs("div",{className:"settings-grid settings-grid--three",children:[e.jsxs(j,{icon:e.jsx(Q,{size:16}),title:e.jsxs("span",{className:"card-title-row",children:["Idle health frequency",x.interval&&e.jsx("span",{className:"modified-dot","aria-label":"Modified"})]}),description:"Frequency of passive health pings when the system is in idle state.",hint:"Minimum safe value: 5000ms. Note: This frequency is overridden when a schedule is active.",children:[e.jsx("div",{className:"card-spacer"}),e.jsxs("div",{className:`input-group ${c?"has-error":""}`,children:[e.jsx("input",{type:"number",value:a.healthCheckIntervalMs,onChange:s=>n({...a,healthCheckIntervalMs:parseInt(s.target.value,10)}),min:S,step:5e3,"aria-invalid":!!c,"aria-describedby":c?"interval-error":"interval-status"}),e.jsx("span",{className:"code-font",children:"MS"})]}),c?e.jsxs("div",{id:"interval-error",className:"input-status input-status--error",role:"alert",children:[e.jsx(G,{size:12}),e.jsx("span",{children:c})]}):e.jsxs("div",{id:"interval-status",className:"input-status input-status--ok",children:[e.jsx(I,{size:12}),e.jsx("span",{children:"Valid frequency"})]})]}),e.jsxs(j,{icon:e.jsx(ae,{size:16}),title:e.jsxs("span",{className:"card-title-row",children:["Deep diagnostic schedule",x.schedule&&e.jsx("span",{className:"modified-dot","aria-label":"Modified"})]}),titleExtra:T?e.jsxs("span",{className:"active-pill",children:[e.jsx(se,{size:11}),e.jsx("span",{children:"Active"})]}):null,description:"Execute intensive reliability bursts (WDA restarts, Cache purges) using standardized Cron syntax.",children:[e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{type:"text",placeholder:"e.g. 0 * * * * (hourly, at minute 0)",value:a.healthCheckSchedule,onChange:s=>n({...a,healthCheckSchedule:s.target.value})})}),e.jsxs("div",{className:"cron-presets",children:[e.jsxs("div",{className:"presets-label",children:[e.jsx(Y,{size:12}),e.jsx("span",{children:"Intent-based presets"})]}),e.jsx("div",{className:"presets-grid presets-grid--stacked",children:ne.map(s=>{const l=a.healthCheckSchedule===s.value;return e.jsxs("button",{type:"button",className:`preset-chip ${l?"active":""}`,onClick:()=>n({...a,healthCheckSchedule:s.value}),"aria-pressed":l,children:[l&&e.jsx(ee,{size:11,className:"preset-chip__check"}),e.jsx("span",{children:s.label})]},s.label)})})]})]}),e.jsxs(j,{icon:e.jsx(O,{size:16}),title:e.jsxs("span",{className:"card-title-row",children:["AI self-healing",x.healing&&e.jsx("span",{className:"modified-dot","aria-label":"Modified"})]}),titleExtra:e.jsxs("label",{className:"switch switch--header",children:[e.jsx("input",{type:"checkbox",checked:a.enableSelfHealing,onChange:s=>n({...a,enableSelfHealing:s.target.checked}),"aria-label":"Toggle AI self-healing"}),e.jsx("span",{className:"slider round"})]}),children:[e.jsx("div",{className:`healing-state ${a.enableSelfHealing?"is-on":"is-off"}`,children:a.enableSelfHealing?"Enabled":"Disabled"}),e.jsx("p",{className:"setting-card-description",children:"Automatically intercept and recover from failing locators using Xenon's 6-tier strategy: etalon recovery, native retry, fuzzy XML, OCR, visual AI, and LLM — before failing a test."}),a.enableSelfHealing&&e.jsxs("div",{className:"healing-events",children:[e.jsxs("div",{className:"healing-events__header",children:[e.jsx(Z,{size:11}),e.jsx("span",{children:"Recent healing events"}),e.jsxs(V,{to:"/selector-health",className:"healing-events__link",children:["Selector health ",e.jsx(te,{size:10})]})]}),y.length>0?e.jsx("ul",{className:"healing-events__list",children:y.map(s=>e.jsxs("li",{className:"healing-event",children:[e.jsx(I,{size:14,className:"healing-event__icon"}),e.jsxs("div",{className:"healing-event__body",children:[e.jsxs("div",{className:"healing-event__row",children:[e.jsx("span",{className:"healing-event__kind",children:ie(s)}),e.jsx("span",{className:"healing-event__when",children:le(s.createdAt)})]}),e.jsx("div",{className:"healing-event__message",title:A(s),children:A(s)})]})]},s.id))}):e.jsx("div",{className:"healing-events__empty",children:$?"No healing events recorded yet — the dashboard will populate as locators are recovered.":"Loading recent events…"})]})]})]})}),(v||C)&&e.jsx(U,{onSave:()=>_(),onDiscard:D,onRestoreDefaults:()=>f(!0),isSaving:C,isValidating:!!c,isDirty:v,saveLabel:R?"Save Configuration":"Resolve errors to save"}),e.jsxs(W,{open:w,onClose:()=>f(!1),title:"Restore default settings?",footer:e.jsxs(e.Fragment,{children:[e.jsx(E,{variant:"secondary",onClick:()=>f(!1),children:"Cancel"}),e.jsx(E,{variant:"danger",onClick:async()=>{f(!1),await L()},children:"Restore defaults"})]}),children:[e.jsx("p",{className:"text-sm text-[var(--text-muted)]",children:"This saves immediately and can't be undone:"}),e.jsxs("ul",{className:"mt-2 list-disc pl-5 text-sm text-[var(--text)] space-y-1",children:[e.jsxs("li",{children:["Idle health frequency goes back to ",m.healthCheckIntervalMs.toLocaleString()," ms"]}),e.jsx("li",{children:"The diagnostic schedule is cleared"}),e.jsx("li",{children:"AI self-healing is turned on"}),e.jsx("li",{children:"Every device's healed-selector count is reset to zero"})]})]})]})};export{Ce as Settings};
@@ -1 +1 @@
1
- import{r as a,p as _,X as v,j as y}from"./index-DbzQAv86.js";import{f as E}from"./time-B8FIgwPE.js";const $=3e3;function k(){const[e,t]=a.useState([]),[r,o]=a.useState([]),[s,i]=a.useState(null),[d,p]=a.useState(""),[S,b]=a.useState(!0),[x,m]=a.useState(null),c=a.useRef(!0),{on:l}=_(),u=a.useCallback(async()=>{try{const[n,f]=await Promise.all([v.getBuilds(),s?v.getSessions({buildId:s,query:d}):Promise.resolve([])]);if(!c.current)return;t(Array.isArray(n)?n:[]),o(Array.isArray(f)?f:[]),m(null)}catch(n){if(!c.current)return;m(n instanceof Error?n.message:String(n))}finally{c.current&&b(!1)}},[s,d]);return a.useEffect(()=>{c.current=!0,u();const n=setInterval(u,$);return()=>{c.current=!1,clearInterval(n)}},[u]),a.useEffect(()=>{const n=()=>u(),f=[l("session_started",n),l("session_stopped",n),l("device_added",n),l("device_removed",n)];return()=>{for(const g of f)g()}},[l,u]),{builds:e,selectedBuildId:s,sessions:r,loading:S,error:x,selectBuild:i,searchQuery:d,setSearchQuery:p,refresh:u}}function h(e){switch(e){case"success":case"passed":case"ended":return"passed";case"failed":case"error":case"timeout":return"failed";case"running":return"running";default:return"other"}}function B(e){const t={all:e.length,passed:0,failed:0,running:0};for(const r of e){const o=h(r.status);o!=="other"&&(t[o]+=1)}return t}function w(e,t){return t==="all"?!0:h(e.status)===t}function C(e,t,r){const o=r.trim().toLowerCase();return e.filter(s=>!(!w(s,t)||o&&!(s.id+" "+(s.name??"")+" "+(s.device_name??"")+" "+(s.device_platform??"")+" "+(s.device_version??"")+" "+(s.node_id??"")).toLowerCase().includes(o)))}function I(e){var r;const t=(r=e.device_name)==null?void 0:r.trim();return t&&t.length>0?t:"Unknown Device"}function T(e){const t=e.device_platform??"";if(!t)return"—";const r=t.toLowerCase();return r==="ios"?"iOS":r==="tvos"?"tvOS":r.charAt(0).toUpperCase()+r.slice(1)}function F(e){return e.device_version?`v${e.device_version}`:""}function M(e){return E(e)}function N(e){if(!e.startTime)return null;const t=Date.parse(e.startTime);if(!Number.isFinite(t))return null;const r=e.endTime?Date.parse(e.endTime):Date.now();return Number.isFinite(r)?Math.max(0,r-t):null}function R(e){if(e==null||!Number.isFinite(e)||e<0)return"—";const t=e/1e3,r=Math.floor(t/3600),o=Math.floor(t%3600/60),i=(t-r*3600-o*60).toFixed(1);return r>0?`${r}h ${o}m ${i}s`:o>0?`${o}m ${i}s`:`${i}s`}function j(e,t=10,r=4){return e?e.length<=t+r+1?e:`${e.slice(0,t)}…${e.slice(-r)}`:""}const D={ready:"border-[var(--color-success)]/40 text-[var(--color-success)]",passed:"border-[var(--color-success)]/40 text-[var(--color-success)]",running:"border-[var(--amber)]/40 text-[var(--amber)]",failed:"border-[var(--red)]/40 text-[var(--red)]",offline:"border-[var(--text-dim)]/40 text-[var(--text-dim)]"},O=({label:e,tone:t})=>y.jsx("span",{className:`inline-flex items-center justify-center h-6 px-2.5 rounded border text-[10px] font-mono font-semibold tracking-wider ${D[t]}`,children:e});export{O as S,h as a,B as b,N as c,I as d,C as e,M as f,R as h,F as o,T as p,j as s,k as u};
1
+ import{r as a,p as _,X as v,j as y}from"./index-BHDlQcd6.js";import{f as E}from"./time-B8FIgwPE.js";const $=3e3;function k(){const[e,t]=a.useState([]),[r,o]=a.useState([]),[s,i]=a.useState(null),[d,p]=a.useState(""),[S,b]=a.useState(!0),[x,m]=a.useState(null),c=a.useRef(!0),{on:l}=_(),u=a.useCallback(async()=>{try{const[n,f]=await Promise.all([v.getBuilds(),s?v.getSessions({buildId:s,query:d}):Promise.resolve([])]);if(!c.current)return;t(Array.isArray(n)?n:[]),o(Array.isArray(f)?f:[]),m(null)}catch(n){if(!c.current)return;m(n instanceof Error?n.message:String(n))}finally{c.current&&b(!1)}},[s,d]);return a.useEffect(()=>{c.current=!0,u();const n=setInterval(u,$);return()=>{c.current=!1,clearInterval(n)}},[u]),a.useEffect(()=>{const n=()=>u(),f=[l("session_started",n),l("session_stopped",n),l("device_added",n),l("device_removed",n)];return()=>{for(const g of f)g()}},[l,u]),{builds:e,selectedBuildId:s,sessions:r,loading:S,error:x,selectBuild:i,searchQuery:d,setSearchQuery:p,refresh:u}}function h(e){switch(e){case"success":case"passed":case"ended":return"passed";case"failed":case"error":case"timeout":return"failed";case"running":return"running";default:return"other"}}function B(e){const t={all:e.length,passed:0,failed:0,running:0};for(const r of e){const o=h(r.status);o!=="other"&&(t[o]+=1)}return t}function w(e,t){return t==="all"?!0:h(e.status)===t}function C(e,t,r){const o=r.trim().toLowerCase();return e.filter(s=>!(!w(s,t)||o&&!(s.id+" "+(s.name??"")+" "+(s.device_name??"")+" "+(s.device_platform??"")+" "+(s.device_version??"")+" "+(s.node_id??"")).toLowerCase().includes(o)))}function I(e){var r;const t=(r=e.device_name)==null?void 0:r.trim();return t&&t.length>0?t:"Unknown Device"}function T(e){const t=e.device_platform??"";if(!t)return"—";const r=t.toLowerCase();return r==="ios"?"iOS":r==="tvos"?"tvOS":r.charAt(0).toUpperCase()+r.slice(1)}function F(e){return e.device_version?`v${e.device_version}`:""}function M(e){return E(e)}function N(e){if(!e.startTime)return null;const t=Date.parse(e.startTime);if(!Number.isFinite(t))return null;const r=e.endTime?Date.parse(e.endTime):Date.now();return Number.isFinite(r)?Math.max(0,r-t):null}function R(e){if(e==null||!Number.isFinite(e)||e<0)return"—";const t=e/1e3,r=Math.floor(t/3600),o=Math.floor(t%3600/60),i=(t-r*3600-o*60).toFixed(1);return r>0?`${r}h ${o}m ${i}s`:o>0?`${o}m ${i}s`:`${i}s`}function j(e,t=10,r=4){return e?e.length<=t+r+1?e:`${e.slice(0,t)}…${e.slice(-r)}`:""}const D={ready:"border-[var(--color-success)]/40 text-[var(--color-success)]",passed:"border-[var(--color-success)]/40 text-[var(--color-success)]",running:"border-[var(--amber)]/40 text-[var(--amber)]",failed:"border-[var(--red)]/40 text-[var(--red)]",offline:"border-[var(--text-dim)]/40 text-[var(--text-dim)]"},O=({label:e,tone:t})=>y.jsx("span",{className:`inline-flex items-center justify-center h-6 px-2.5 rounded border text-[10px] font-mono font-semibold tracking-wider ${D[t]}`,children:e});export{O as S,h as a,B as b,N as c,I as d,C as e,M as f,R as h,F as o,T as p,j as s,k as u};
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-DbzQAv86.js";/**
1
+ import{c as e}from"./index-BHDlQcd6.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{x as w,r as n,j as e,D as I,V as f,q as $}from"./index-DbzQAv86.js";/* empty css */import{a as F}from"./time-B8FIgwPE.js";import{T as E,a as M,b as T,c as u,d as U,e as j}from"./Table-EHWJCCzN.js";import{F as Y}from"./FieldGroup-oWAJBBAu.js";import{P as R}from"./page-header-CqaoOYPH.js";import{M as K}from"./Modal-LXKxgyV7.js";import{B as D}from"./button-Do8mBvkU.js";import{I as Q}from"./input-CKKtQEU5.js";import{S as P}from"./select-DVYczzD1.js";import{l as W}from"./users-DUb_rmxk.js";import{P as N}from"./plus-CJfVcjJv.js";import{A as X}from"./arrow-left-QDqb-yc1.js";import{T as L}from"./trash-2-DJgpvIoq.js";import"./utils-CDN07tui.js";const B={SUPER_ADMIN:"Super admin",ADMIN:"Admin",MEMBER:"Member"};async function p(i,r){const t=await fetch(i,{credentials:"include",...r});if(!t.ok){const c=await t.json().catch(()=>({}));throw Object.assign(new Error(c.error||`HTTP ${t.status}`),{status:t.status})}return t.json()}const ge=()=>{const{toast:i}=w(),[r,t]=n.useState([]),[c,m]=n.useState(!0),[l,h]=n.useState(!1),[x,d]=n.useState(!1),[_,C]=n.useState(null),v=async()=>{m(!0);try{const a=await p("/xenon/api/teams");t(a),h(!1)}catch(a){a.status===403?h(!0):i(`Failed to load teams: ${a.message}`,"error")}finally{m(!1)}};if(n.useEffect(()=>{v()},[]),c)return e.jsxs("div",{className:"settings-loading",children:[e.jsx(I,{className:"animate-spin",size:32}),e.jsx("span",{children:"Loading teams…"})]});if(l)return e.jsx("div",{className:"settings-container",children:e.jsx(R,{icon:f,title:"Teams",subtitle:e.jsxs(e.Fragment,{children:["Your key lacks the ",e.jsx("code",{children:"admin"})," scope. Ask an administrator to promote your key before you can manage teams."]})})});if(_)return e.jsx(se,{team:_,onBack:()=>{C(null),v()}});const S=r.reduce((a,o)=>a+o.deviceCount,0),y=r.reduce((a,o)=>a+o.memberCount,0);return e.jsxs("div",{className:"settings-container",children:[e.jsx(R,{icon:f,title:"Teams",subtitle:"Group devices and members by team. Devices with no team assignment stay in the shared pool — visible to every authenticated user.",action:r.length>0?e.jsxs("button",{type:"button",className:"page-header-action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"New team"})]}):void 0}),e.jsxs("div",{className:"settings-content",children:[r.length>0&&e.jsxs("div",{className:"stat-strip",children:[e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value stat-strip__value--accent",children:r.length}),e.jsx("div",{className:"stat-strip__label",children:"Teams"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:y}),e.jsx("div",{className:"stat-strip__label",children:"Members"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:S}),e.jsx("div",{className:"stat-strip__label",children:"Devices assigned"})]})]}),r.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(f,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No teams yet"}),e.jsx("p",{className:"empty-state__copy",children:"Every device is in the shared pool until you create a team and assign it. Teams let you scope an API key to a subset of devices — useful for separating CI runners, staging fleets, or per-team device pools."}),e.jsxs("button",{type:"button",className:"page-header-action empty-state__action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"Create your first team"})]})]}):e.jsx("div",{className:"team-grid",children:r.map(a=>e.jsxs("button",{type:"button",className:"team-card",onClick:()=>C(a),children:[e.jsxs("div",{className:"team-card__header",children:[e.jsx("div",{className:"team-card__icon",children:e.jsx(f,{size:16})}),e.jsxs("div",{className:"team-card__title",children:[e.jsx("div",{className:"team-card__name",children:a.name}),e.jsx("div",{className:"team-card__id",children:a.id.slice(0,8)})]}),e.jsx("span",{className:"team-card__delete",role:"button",tabIndex:0,onClick:o=>o.stopPropagation(),children:e.jsx(ee,{team:a,onDeleted:v})})]}),e.jsxs("div",{className:"team-card__metrics",children:[e.jsxs("div",{className:"team-card__metric",children:[e.jsx($,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.deviceCount}),e.jsx("span",{className:"team-card__metric-label",children:"devices"})]}),e.jsxs("div",{className:"team-card__metric",children:[e.jsx(f,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.memberCount}),e.jsx("span",{className:"team-card__metric-label",children:"members"})]})]}),e.jsxs("div",{className:"team-card__footer",children:["Created ",F(a.createdAt)]})]},a.id))})]}),x&&e.jsx(Z,{onClose:()=>d(!1),onCreated:()=>{d(!1),v()}})]})},Z=({onClose:i,onCreated:r})=>{const{toast:t}=w(),[c,m]=n.useState(""),[l,h]=n.useState(!1),x=async()=>{if(!c.trim())return t("Name required","error");h(!0);try{await p("/xenon/api/teams",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:c.trim()})}),t("Team created","success"),r()}catch(d){t(d.message,"error")}finally{h(!1)}};return e.jsx(K,{open:!0,onClose:i,title:"New team",footer:e.jsxs(e.Fragment,{children:[e.jsx(D,{variant:"secondary",onClick:i,disabled:l,children:"Cancel"}),e.jsxs(D,{variant:"primary",onClick:x,disabled:l,children:[l?e.jsx(I,{className:"animate-spin",size:18}):e.jsx(N,{size:18}),l?"Creating…":"Create"]})]}),children:e.jsx("div",{style:{display:"flex",flexDirection:"column"},children:e.jsx(Y,{label:"Name",htmlFor:"team-name",children:e.jsx(Q,{id:"team-name",type:"text",value:c,onChange:d=>m(d.target.value),placeholder:"android-team, qa-ios, …",className:"w-full",autoFocus:!0})})})})},ee=({team:i,onDeleted:r})=>{const{toast:t}=w(),[c,m]=n.useState(!1),l=async h=>{if(h.stopPropagation(),!!window.confirm(`Delete team "${i.name}"? It must have no devices and no members.`)){m(!0);try{await p(`/xenon/api/teams/${i.id}`,{method:"DELETE"}),t("Team deleted","success"),r()}catch(x){t(x.message,"error")}finally{m(!1)}}};return e.jsxs("button",{className:"reset-btn",disabled:c,onClick:l,style:{color:"var(--status-error-fg)"},children:[e.jsx(L,{size:14}),c?"Deleting…":"Delete"]})},se=({team:i,onBack:r})=>{const{toast:t}=w(),[c,m]=n.useState([]),[l,h]=n.useState([]),[x,d]=n.useState([]),[_,C]=n.useState([]),[v,S]=n.useState(!0),[y,a]=n.useState(""),[o,k]=n.useState(""),b=async()=>{S(!0);try{const[s,g,V]=await Promise.all([p(`/xenon/api/teams/${i.id}/members`),W(),p("/xenon/api/device")]);m(s),h(g);const z=V;d(z.filter(A=>A.teamId===i.id)),C(z.filter(A=>!A.teamId))}catch(s){t(`Failed to load team detail: ${s.message}`,"error")}finally{S(!1)}};n.useEffect(()=>{b()},[i.id]);const O=async()=>{if(y)try{await p(`/xenon/api/teams/${i.id}/members`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userId:y})}),t("Member added","success"),a(""),b()}catch(s){t(s.message,"error")}},H=async s=>{if(window.confirm("Remove this member from the team?"))try{await p(`/xenon/api/teams/${i.id}/members/${encodeURIComponent(s)}`,{method:"DELETE"}),t("Member removed","success"),b()}catch(g){t(g.message,"error")}},J=async()=>{if(o)try{await p(`/xenon/api/device/${encodeURIComponent(o)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:i.id})}),t("Device assigned","success"),k(""),b()}catch(s){t(s.message,"error")}},q=async s=>{if(window.confirm("Return this device to the shared pool?"))try{await p(`/xenon/api/device/${encodeURIComponent(s)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:null})}),t("Device unassigned","success"),b()}catch(g){t(g.message,"error")}},G=l.filter(s=>!c.some(g=>g.userId===s.id));return e.jsxs("div",{className:"settings-container mesh-gradient-infra",children:[e.jsxs("div",{className:"settings-header",children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("button",{onClick:r,className:"reset-btn",style:{padding:"4px 8px"},"aria-label":"Back",children:e.jsx(X,{size:18})}),e.jsxs("div",{className:"settings-title-group",children:[e.jsx(f,{className:"settings-icon infra-icon",size:28}),e.jsx("h2",{children:i.name})]})]}),e.jsx("p",{className:"settings-subtitle",children:'Manage members and devices assigned to this team. To add a device, go to Devices and use "Assign to team."'})]}),v?e.jsx("div",{className:"settings-loading",children:e.jsx(I,{className:"animate-spin",size:32})}):e.jsxs("div",{className:"settings-content",style:{display:"flex",flexDirection:"column",gap:24},children:[e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Members (",c.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(P,{value:y,onChange:s=>a(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a user to add…"}),G.map(s=>e.jsxs("option",{value:s.id,children:[s.name," (",s.email,") — ",B[s.role]]},s.id))]}),e.jsxs(D,{variant:"primary",onClick:O,disabled:!y,children:[e.jsx(N,{size:16})," Add"]})]}),c.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No members yet. Add a user above."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Email"}),e.jsx(u,{children:"Role"}),e.jsx(u,{children:"Added"}),e.jsx(u,{style:{textAlign:"right"},children:"Action"})]})}),e.jsx(U,{children:c.map(s=>e.jsxs(T,{children:[e.jsx(j,{children:s.name}),e.jsx(j,{children:e.jsx("code",{style:{fontSize:"11px",color:"var(--text-muted)"},children:s.email})}),e.jsx(j,{children:B[s.role]}),e.jsx(j,{children:F(s.addedAt)}),e.jsx(j,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>H(s.userId),style:{color:"var(--status-error-fg)"},"aria-label":"Remove member",title:"Remove member",children:e.jsx(L,{size:14})})})]},s.userId))})]})]})]}),e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Devices (",x.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(P,{value:o,onChange:s=>k(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a shared-pool device to assign…"}),_.map(s=>e.jsxs("option",{value:s.udid,children:[s.name," (",s.platform,") — ",s.udid.slice(0,12),"…"]},s.udid))]}),e.jsxs(D,{variant:"primary",onClick:J,disabled:!o,children:[e.jsx(N,{size:16})," Assign"]})]}),x.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No devices assigned. Use the Devices page to move devices into this team."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Platform"}),e.jsx(u,{children:"UDID"}),e.jsx(u,{style:{textAlign:"right"},children:"Actions"})]})}),e.jsx(U,{children:x.map(s=>e.jsxs(T,{children:[e.jsxs(j,{children:[e.jsx($,{size:14,style:{marginRight:6,verticalAlign:"middle"}}),s.name]}),e.jsx(j,{children:s.platform}),e.jsx(j,{children:e.jsxs("code",{style:{fontSize:"11px"},children:[s.udid.slice(0,14),"…"]})}),e.jsx(j,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>q(s.udid),children:"Return to shared pool"})})]},s.udid))})]})]})]})]})]})};export{ge as Teams};
1
+ import{x as w,r as n,j as e,D as I,V as f,q as $}from"./index-BHDlQcd6.js";/* empty css */import{a as F}from"./time-B8FIgwPE.js";import{T as E,a as M,b as T,c as u,d as U,e as j}from"./Table-DRkkM4OR.js";import{F as Y}from"./FieldGroup-DmYZP2j7.js";import{P as R}from"./page-header-DhD-Kua9.js";import{M as K}from"./Modal-C5W8lMJ6.js";import{B as D}from"./button-DrqhMLZ0.js";import{I as Q}from"./input-Ilib6feD.js";import{S as P}from"./select-Cfv-njLE.js";import{l as W}from"./users-DyB6E3FF.js";import{P as N}from"./plus-B75ovH3O.js";import{A as X}from"./arrow-left-CaOe18yC.js";import{T as L}from"./trash-2-BcQxmJP-.js";import"./utils-CDN07tui.js";const B={SUPER_ADMIN:"Super admin",ADMIN:"Admin",MEMBER:"Member"};async function p(i,r){const t=await fetch(i,{credentials:"include",...r});if(!t.ok){const c=await t.json().catch(()=>({}));throw Object.assign(new Error(c.error||`HTTP ${t.status}`),{status:t.status})}return t.json()}const ge=()=>{const{toast:i}=w(),[r,t]=n.useState([]),[c,m]=n.useState(!0),[l,h]=n.useState(!1),[x,d]=n.useState(!1),[_,C]=n.useState(null),v=async()=>{m(!0);try{const a=await p("/xenon/api/teams");t(a),h(!1)}catch(a){a.status===403?h(!0):i(`Failed to load teams: ${a.message}`,"error")}finally{m(!1)}};if(n.useEffect(()=>{v()},[]),c)return e.jsxs("div",{className:"settings-loading",children:[e.jsx(I,{className:"animate-spin",size:32}),e.jsx("span",{children:"Loading teams…"})]});if(l)return e.jsx("div",{className:"settings-container",children:e.jsx(R,{icon:f,title:"Teams",subtitle:e.jsxs(e.Fragment,{children:["Your key lacks the ",e.jsx("code",{children:"admin"})," scope. Ask an administrator to promote your key before you can manage teams."]})})});if(_)return e.jsx(se,{team:_,onBack:()=>{C(null),v()}});const S=r.reduce((a,o)=>a+o.deviceCount,0),y=r.reduce((a,o)=>a+o.memberCount,0);return e.jsxs("div",{className:"settings-container",children:[e.jsx(R,{icon:f,title:"Teams",subtitle:"Group devices and members by team. Devices with no team assignment stay in the shared pool — visible to every authenticated user.",action:r.length>0?e.jsxs("button",{type:"button",className:"page-header-action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"New team"})]}):void 0}),e.jsxs("div",{className:"settings-content",children:[r.length>0&&e.jsxs("div",{className:"stat-strip",children:[e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value stat-strip__value--accent",children:r.length}),e.jsx("div",{className:"stat-strip__label",children:"Teams"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:y}),e.jsx("div",{className:"stat-strip__label",children:"Members"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:S}),e.jsx("div",{className:"stat-strip__label",children:"Devices assigned"})]})]}),r.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(f,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No teams yet"}),e.jsx("p",{className:"empty-state__copy",children:"Every device is in the shared pool until you create a team and assign it. Teams let you scope an API key to a subset of devices — useful for separating CI runners, staging fleets, or per-team device pools."}),e.jsxs("button",{type:"button",className:"page-header-action empty-state__action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"Create your first team"})]})]}):e.jsx("div",{className:"team-grid",children:r.map(a=>e.jsxs("button",{type:"button",className:"team-card",onClick:()=>C(a),children:[e.jsxs("div",{className:"team-card__header",children:[e.jsx("div",{className:"team-card__icon",children:e.jsx(f,{size:16})}),e.jsxs("div",{className:"team-card__title",children:[e.jsx("div",{className:"team-card__name",children:a.name}),e.jsx("div",{className:"team-card__id",children:a.id.slice(0,8)})]}),e.jsx("span",{className:"team-card__delete",role:"button",tabIndex:0,onClick:o=>o.stopPropagation(),children:e.jsx(ee,{team:a,onDeleted:v})})]}),e.jsxs("div",{className:"team-card__metrics",children:[e.jsxs("div",{className:"team-card__metric",children:[e.jsx($,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.deviceCount}),e.jsx("span",{className:"team-card__metric-label",children:"devices"})]}),e.jsxs("div",{className:"team-card__metric",children:[e.jsx(f,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.memberCount}),e.jsx("span",{className:"team-card__metric-label",children:"members"})]})]}),e.jsxs("div",{className:"team-card__footer",children:["Created ",F(a.createdAt)]})]},a.id))})]}),x&&e.jsx(Z,{onClose:()=>d(!1),onCreated:()=>{d(!1),v()}})]})},Z=({onClose:i,onCreated:r})=>{const{toast:t}=w(),[c,m]=n.useState(""),[l,h]=n.useState(!1),x=async()=>{if(!c.trim())return t("Name required","error");h(!0);try{await p("/xenon/api/teams",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:c.trim()})}),t("Team created","success"),r()}catch(d){t(d.message,"error")}finally{h(!1)}};return e.jsx(K,{open:!0,onClose:i,title:"New team",footer:e.jsxs(e.Fragment,{children:[e.jsx(D,{variant:"secondary",onClick:i,disabled:l,children:"Cancel"}),e.jsxs(D,{variant:"primary",onClick:x,disabled:l,children:[l?e.jsx(I,{className:"animate-spin",size:18}):e.jsx(N,{size:18}),l?"Creating…":"Create"]})]}),children:e.jsx("div",{style:{display:"flex",flexDirection:"column"},children:e.jsx(Y,{label:"Name",htmlFor:"team-name",children:e.jsx(Q,{id:"team-name",type:"text",value:c,onChange:d=>m(d.target.value),placeholder:"android-team, qa-ios, …",className:"w-full",autoFocus:!0})})})})},ee=({team:i,onDeleted:r})=>{const{toast:t}=w(),[c,m]=n.useState(!1),l=async h=>{if(h.stopPropagation(),!!window.confirm(`Delete team "${i.name}"? It must have no devices and no members.`)){m(!0);try{await p(`/xenon/api/teams/${i.id}`,{method:"DELETE"}),t("Team deleted","success"),r()}catch(x){t(x.message,"error")}finally{m(!1)}}};return e.jsxs("button",{className:"reset-btn",disabled:c,onClick:l,style:{color:"var(--status-error-fg)"},children:[e.jsx(L,{size:14}),c?"Deleting…":"Delete"]})},se=({team:i,onBack:r})=>{const{toast:t}=w(),[c,m]=n.useState([]),[l,h]=n.useState([]),[x,d]=n.useState([]),[_,C]=n.useState([]),[v,S]=n.useState(!0),[y,a]=n.useState(""),[o,k]=n.useState(""),b=async()=>{S(!0);try{const[s,g,V]=await Promise.all([p(`/xenon/api/teams/${i.id}/members`),W(),p("/xenon/api/device")]);m(s),h(g);const z=V;d(z.filter(A=>A.teamId===i.id)),C(z.filter(A=>!A.teamId))}catch(s){t(`Failed to load team detail: ${s.message}`,"error")}finally{S(!1)}};n.useEffect(()=>{b()},[i.id]);const O=async()=>{if(y)try{await p(`/xenon/api/teams/${i.id}/members`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userId:y})}),t("Member added","success"),a(""),b()}catch(s){t(s.message,"error")}},H=async s=>{if(window.confirm("Remove this member from the team?"))try{await p(`/xenon/api/teams/${i.id}/members/${encodeURIComponent(s)}`,{method:"DELETE"}),t("Member removed","success"),b()}catch(g){t(g.message,"error")}},J=async()=>{if(o)try{await p(`/xenon/api/device/${encodeURIComponent(o)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:i.id})}),t("Device assigned","success"),k(""),b()}catch(s){t(s.message,"error")}},q=async s=>{if(window.confirm("Return this device to the shared pool?"))try{await p(`/xenon/api/device/${encodeURIComponent(s)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:null})}),t("Device unassigned","success"),b()}catch(g){t(g.message,"error")}},G=l.filter(s=>!c.some(g=>g.userId===s.id));return e.jsxs("div",{className:"settings-container mesh-gradient-infra",children:[e.jsxs("div",{className:"settings-header",children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("button",{onClick:r,className:"reset-btn",style:{padding:"4px 8px"},"aria-label":"Back",children:e.jsx(X,{size:18})}),e.jsxs("div",{className:"settings-title-group",children:[e.jsx(f,{className:"settings-icon infra-icon",size:28}),e.jsx("h2",{children:i.name})]})]}),e.jsx("p",{className:"settings-subtitle",children:'Manage members and devices assigned to this team. To add a device, go to Devices and use "Assign to team."'})]}),v?e.jsx("div",{className:"settings-loading",children:e.jsx(I,{className:"animate-spin",size:32})}):e.jsxs("div",{className:"settings-content",style:{display:"flex",flexDirection:"column",gap:24},children:[e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Members (",c.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(P,{value:y,onChange:s=>a(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a user to add…"}),G.map(s=>e.jsxs("option",{value:s.id,children:[s.name," (",s.email,") — ",B[s.role]]},s.id))]}),e.jsxs(D,{variant:"primary",onClick:O,disabled:!y,children:[e.jsx(N,{size:16})," Add"]})]}),c.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No members yet. Add a user above."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Email"}),e.jsx(u,{children:"Role"}),e.jsx(u,{children:"Added"}),e.jsx(u,{style:{textAlign:"right"},children:"Action"})]})}),e.jsx(U,{children:c.map(s=>e.jsxs(T,{children:[e.jsx(j,{children:s.name}),e.jsx(j,{children:e.jsx("code",{style:{fontSize:"11px",color:"var(--text-muted)"},children:s.email})}),e.jsx(j,{children:B[s.role]}),e.jsx(j,{children:F(s.addedAt)}),e.jsx(j,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>H(s.userId),style:{color:"var(--status-error-fg)"},"aria-label":"Remove member",title:"Remove member",children:e.jsx(L,{size:14})})})]},s.userId))})]})]})]}),e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Devices (",x.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(P,{value:o,onChange:s=>k(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a shared-pool device to assign…"}),_.map(s=>e.jsxs("option",{value:s.udid,children:[s.name," (",s.platform,") — ",s.udid.slice(0,12),"…"]},s.udid))]}),e.jsxs(D,{variant:"primary",onClick:J,disabled:!o,children:[e.jsx(N,{size:16})," Assign"]})]}),x.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No devices assigned. Use the Devices page to move devices into this team."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Platform"}),e.jsx(u,{children:"UDID"}),e.jsx(u,{style:{textAlign:"right"},children:"Actions"})]})}),e.jsx(U,{children:x.map(s=>e.jsxs(T,{children:[e.jsxs(j,{children:[e.jsx($,{size:14,style:{marginRight:6,verticalAlign:"middle"}}),s.name]}),e.jsx(j,{children:s.platform}),e.jsx(j,{children:e.jsxs("code",{style:{fontSize:"11px"},children:[s.udid.slice(0,14),"…"]})}),e.jsx(j,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>q(s.udid),children:"Return to shared pool"})})]},s.udid))})]})]})]})]})]})};export{ge as Teams};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-DbzQAv86.js";/**
1
+ import{c as a}from"./index-BHDlQcd6.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-DbzQAv86.js";/**
1
+ import{c as a}from"./index-BHDlQcd6.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{r as t,h as u}from"./index-DbzQAv86.js";function n(){const[s,r]=t.useState(null);return t.useEffect(()=>{let e=!0;return u().then(o=>e&&r(o.passwordReset)),()=>{e=!1}},[]),s}export{n as u};
1
+ import{r as t,h as u}from"./index-BHDlQcd6.js";function n(){const[s,r]=t.useState(null);return t.useEffect(()=>{let e=!0;return u().then(o=>e&&r(o.passwordReset)),()=>{e=!1}},[]),s}export{n as u};
@@ -1,4 +1,4 @@
1
- import{c as w,b as k,r as a,j as e,_ as C}from"./index-DbzQAv86.js";import{l as M,d as R,c as I,a as A,u as D}from"./users-DUb_rmxk.js";import{f as U}from"./time-B8FIgwPE.js";import{P}from"./page-header-CqaoOYPH.js";import{B as y}from"./button-Do8mBvkU.js";import{I as N}from"./input-CKKtQEU5.js";import{S as g}from"./select-DVYczzD1.js";import{P as B}from"./plus-CJfVcjJv.js";import{T as L}from"./trash-2-DJgpvIoq.js";import"./utils-CDN07tui.js";/**
1
+ import{c as w,b as k,r as a,j as e,_ as C}from"./index-BHDlQcd6.js";import{l as M,d as R,c as I,a as A,u as D}from"./users-DyB6E3FF.js";import{f as U}from"./time-B8FIgwPE.js";import{P}from"./page-header-DhD-Kua9.js";import{B as y}from"./button-DrqhMLZ0.js";import{I as N}from"./input-Ilib6feD.js";import{S as g}from"./select-Cfv-njLE.js";import{P as B}from"./plus-B75ovH3O.js";import{T as L}from"./trash-2-BcQxmJP-.js";import"./utils-CDN07tui.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{o as s}from"./index-DbzQAv86.js";const o="/xenon/api/users";async function c(){const t=await fetch(`${o}/`,{credentials:"include"});if(!t.ok)throw new Error(`listUsers failed (${t.status})`);return t.json()}async function i(t){const e=await fetch(`${o}/`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...s},body:JSON.stringify(t)});if(!e.ok){const n=await e.json().catch(()=>({}));throw new Error(n.error||`createUser failed (${e.status})`)}return e.json()}async function d(t,e){const n=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`updateUser failed (${n.status})`)}return n.json()}async function l(t){const e=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"DELETE",credentials:"include"});if(!e.ok&&e.status!==204){const n=await e.json().catch(()=>({}));throw new Error(n.error||`deleteUser failed (${e.status})`)}}async function u(t){const e=await fetch(`${o}/${encodeURIComponent(t)}/reset-link`,{method:"POST",credentials:"include"}),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(n.error||`Reset link failed (${e.status})`);return n}export{i as a,u as c,l as d,c as l,d as u};
1
+ import{o as s}from"./index-BHDlQcd6.js";const o="/xenon/api/users";async function c(){const t=await fetch(`${o}/`,{credentials:"include"});if(!t.ok)throw new Error(`listUsers failed (${t.status})`);return t.json()}async function i(t){const e=await fetch(`${o}/`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...s},body:JSON.stringify(t)});if(!e.ok){const n=await e.json().catch(()=>({}));throw new Error(n.error||`createUser failed (${e.status})`)}return e.json()}async function d(t,e){const n=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`updateUser failed (${n.status})`)}return n.json()}async function l(t){const e=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"DELETE",credentials:"include"});if(!e.ok&&e.status!==204){const n=await e.json().catch(()=>({}));throw new Error(n.error||`deleteUser failed (${e.status})`)}}async function u(t){const e=await fetch(`${o}/${encodeURIComponent(t)}/reset-link`,{method:"POST",credentials:"include"}),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(n.error||`Reset link failed (${e.status})`);return n}export{i as a,u as c,l as d,c as l,d as u};
@@ -1 +1 @@
1
- import{x as P,r,X as c,j as e,H as u,D as b,C as U,J as D}from"./index-DbzQAv86.js";/* empty css */import{F as S}from"./FieldGroup-oWAJBBAu.js";import{P as H}from"./page-header-CqaoOYPH.js";import{T as O}from"./trash-2-DJgpvIoq.js";import{P as x}from"./plus-CJfVcjJv.js";import{C as V}from"./circle-x-BPKWtaZx.js";import{C as B}from"./circle-check-Dkg-Ka4_.js";import{Z as I}from"./zap-BLSqrVs9.js";import{A as J}from"./activity-CHfTPuBn.js";const C=[{id:"device_offline",label:"Device offline",icon:U,tone:"red"},{id:"device_new",label:"New device",icon:x,tone:"green"},{id:"session_failed",label:"Session failed",icon:V,tone:"amber"},{id:"selector_health_digest",label:"Selector health digest",icon:D,tone:"green"}],X=["udid","host","name","sessionId","failureReason","eventType","platform"],ae=()=>{const{toast:o}=P(),[g,T]=r.useState([]),[A,w]=r.useState(!0),[l,j]=r.useState(""),[d,k]=r.useState(["device_offline","session_failed"]),[h,f]=r.useState(!1),[v,N]=r.useState(!1),[n,_]=r.useState(!1),[y,m]=r.useState("");r.useEffect(()=>{p()},[]);const p=async()=>{w(!0);try{const s=await c.getWebhookConfigs();T(s||[])}catch(s){console.error("Failed to load webhook configs",s),o("Failed to load webhooks.","error")}finally{w(!1)}},z=async()=>{if(!l.trim()){o("Webhook URL is required.","error");return}if(d.length===0){o("Select at least one trigger event.","error");return}f(!0);try{await c.addWebhookConfig(l.trim(),d,"slack",y||void 0),j(""),m(""),_(!1),k(["device_offline","session_failed"]),await p(),o("Webhook saved.","success")}catch(s){console.error("Failed to add webhook",s),o(`Failed to add webhook: ${s.message||"unknown error"}`,"error")}finally{f(!1)}},E=async(s,t)=>{if(window.confirm(`Remove webhook for "${t}"?`))try{await c.deleteWebhookConfig(s),await p(),o("Webhook removed.","success")}catch(a){console.error("Failed to delete webhook",a),o(`Failed to delete: ${a.message||"unknown error"}`,"error")}},L=async()=>{if(l.trim()){N(!0);try{await c.testWebhook(l.trim(),"slack"),o("Test payload delivered.","success")}catch(s){o(`Test failed: ${s.message||"check the URL"}`,"error")}finally{N(!1)}}},$=s=>{k(t=>t.includes(s)?t.filter(a=>a!==s):[...t,s])},R=s=>{m(t=>t+`{{${s}}} `)};return e.jsxs("div",{className:"settings-container",children:[e.jsx(H,{icon:u,title:"Notifications",subtitle:"Configure Slack or generic webhooks to receive alerts for critical infrastructure events."}),e.jsxs("div",{className:"settings-content",children:[A?e.jsxs("div",{className:"settings-loading",style:{height:200},children:[e.jsx(b,{className:"animate-spin",size:28}),e.jsx("span",{children:"Loading webhooks…"})]}):g.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(u,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No webhooks configured"}),e.jsx("p",{className:"empty-state__copy",children:"Wire up a Slack incoming webhook or any HTTPS endpoint to receive alerts when devices go offline, new devices register, or sessions fail. Configure your first webhook below."})]}):e.jsx("div",{className:"webhook-list-grid",children:g.map(s=>{const t=(()=>{try{return JSON.parse(s.events)}catch{return[]}})();return e.jsxs("div",{className:"webhook-row-card",children:[e.jsxs("div",{className:"webhook-row-card__header",children:[e.jsx("span",{className:`pill-chip ${s.payloadTemplate?"pill-chip--admin":"pill-chip--scope"}`,children:s.payloadTemplate?"CUSTOM":"SLACK"}),e.jsx("span",{className:"webhook-row-card__url",title:s.url,children:s.url}),e.jsxs("button",{type:"button",className:"row-action row-action--danger",onClick:()=>E(s.id,s.url),"aria-label":`Remove webhook for ${s.url}`,children:[e.jsx(O,{size:13}),e.jsx("span",{children:"Remove"})]})]}),e.jsx("div",{className:"webhook-row-card__events",children:t.length===0?e.jsx("span",{className:"webhook-row-card__no-events",children:"No triggers"}):t.map(a=>{const i=C.find(F=>F.id===a),W=(i==null?void 0:i.icon)??u;return e.jsxs("span",{className:`event-pill event-pill--${(i==null?void 0:i.tone)||"gray"}`,children:[e.jsx(W,{size:11}),(i==null?void 0:i.label)||a]},a)})})]},s.id)})}),e.jsxs("div",{className:"add-webhook-card",children:[e.jsxs("div",{className:"add-webhook-card__header",children:[e.jsx(x,{size:14}),e.jsx("span",{children:"Add a new webhook"})]}),e.jsxs("div",{className:"add-webhook-card__body",children:[e.jsx(S,{label:"Webhook URL",description:"The endpoint we POST webhook events to.",htmlFor:"webhook-url",children:e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{id:"webhook-url",type:"url",placeholder:"https://hooks.slack.com/services/...",value:l,onChange:s=>j(s.target.value)})})}),e.jsx(S,{label:"Trigger events",children:e.jsx("div",{className:"event-toggle-grid",children:C.map(s=>{const t=d.includes(s.id),a=s.icon;return e.jsxs("button",{type:"button",role:"checkbox","aria-checked":t,className:`event-toggle event-toggle--${s.tone} ${t?"is-selected":""}`,onClick:()=>$(s.id),children:[e.jsx(a,{size:14,className:"event-toggle__lead"}),e.jsx("span",{children:s.label}),t&&e.jsx(B,{size:14,className:"event-toggle__check"})]},s.id)})})}),e.jsxs("div",{className:"template-section",children:[e.jsxs("button",{type:"button",className:"template-section__toggle",onClick:()=>_(!n),"aria-expanded":n,children:[e.jsx(I,{size:14,className:n?"tone-amber":"tone-dim"}),e.jsx("span",{children:"Use custom payload (optional)"}),e.jsx("span",{className:"template-section__indicator",children:n?"−":"+"})]}),n&&e.jsxs("div",{className:"template-editor",children:[e.jsx("p",{className:"template-editor__hint",children:"Define a JSON or text template. Click a variable below to insert it."}),e.jsx("div",{className:"template-editor__chips",children:X.map(s=>e.jsx("button",{type:"button",className:"template-editor__chip",onClick:()=>R(s),children:`{{${s}}}`},s))}),e.jsx("textarea",{className:"template-editor__textarea",placeholder:'Example: { "text": "Alert: Device {{udid}} is offline!" }',value:y,onChange:s=>m(s.target.value),rows:4})]})]})]}),e.jsxs("div",{className:"add-webhook-card__footer",children:[e.jsxs("button",{type:"button",className:"page-header-action page-header-action--ghost",onClick:L,disabled:!l.trim()||v,children:[v?e.jsx(b,{size:14,className:"animate-spin"}):e.jsx(J,{size:14}),e.jsx("span",{children:"Test payload"})]}),e.jsxs("button",{type:"button",className:"page-header-action",onClick:z,disabled:h||!l.trim(),children:[h?e.jsx(b,{size:14,className:"animate-spin"}):e.jsx(x,{size:14}),e.jsx("span",{children:h?"Saving…":"Save webhook"})]})]})]})]})]})};export{ae as WebhookSettings};
1
+ import{x as P,r,X as c,j as e,H as u,D as b,C as U,J as D}from"./index-BHDlQcd6.js";/* empty css */import{F as S}from"./FieldGroup-DmYZP2j7.js";import{P as H}from"./page-header-DhD-Kua9.js";import{T as O}from"./trash-2-BcQxmJP-.js";import{P as x}from"./plus-B75ovH3O.js";import{C as V}from"./circle-x-BhUzyxKR.js";import{C as B}from"./circle-check-Cokzy9a2.js";import{Z as I}from"./zap-CqGOjAjn.js";import{A as J}from"./activity-B6x69oOy.js";const C=[{id:"device_offline",label:"Device offline",icon:U,tone:"red"},{id:"device_new",label:"New device",icon:x,tone:"green"},{id:"session_failed",label:"Session failed",icon:V,tone:"amber"},{id:"selector_health_digest",label:"Selector health digest",icon:D,tone:"green"}],X=["udid","host","name","sessionId","failureReason","eventType","platform"],ae=()=>{const{toast:o}=P(),[g,T]=r.useState([]),[A,w]=r.useState(!0),[l,j]=r.useState(""),[d,k]=r.useState(["device_offline","session_failed"]),[h,f]=r.useState(!1),[v,N]=r.useState(!1),[n,_]=r.useState(!1),[y,m]=r.useState("");r.useEffect(()=>{p()},[]);const p=async()=>{w(!0);try{const s=await c.getWebhookConfigs();T(s||[])}catch(s){console.error("Failed to load webhook configs",s),o("Failed to load webhooks.","error")}finally{w(!1)}},z=async()=>{if(!l.trim()){o("Webhook URL is required.","error");return}if(d.length===0){o("Select at least one trigger event.","error");return}f(!0);try{await c.addWebhookConfig(l.trim(),d,"slack",y||void 0),j(""),m(""),_(!1),k(["device_offline","session_failed"]),await p(),o("Webhook saved.","success")}catch(s){console.error("Failed to add webhook",s),o(`Failed to add webhook: ${s.message||"unknown error"}`,"error")}finally{f(!1)}},E=async(s,t)=>{if(window.confirm(`Remove webhook for "${t}"?`))try{await c.deleteWebhookConfig(s),await p(),o("Webhook removed.","success")}catch(a){console.error("Failed to delete webhook",a),o(`Failed to delete: ${a.message||"unknown error"}`,"error")}},L=async()=>{if(l.trim()){N(!0);try{await c.testWebhook(l.trim(),"slack"),o("Test payload delivered.","success")}catch(s){o(`Test failed: ${s.message||"check the URL"}`,"error")}finally{N(!1)}}},$=s=>{k(t=>t.includes(s)?t.filter(a=>a!==s):[...t,s])},R=s=>{m(t=>t+`{{${s}}} `)};return e.jsxs("div",{className:"settings-container",children:[e.jsx(H,{icon:u,title:"Notifications",subtitle:"Configure Slack or generic webhooks to receive alerts for critical infrastructure events."}),e.jsxs("div",{className:"settings-content",children:[A?e.jsxs("div",{className:"settings-loading",style:{height:200},children:[e.jsx(b,{className:"animate-spin",size:28}),e.jsx("span",{children:"Loading webhooks…"})]}):g.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(u,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No webhooks configured"}),e.jsx("p",{className:"empty-state__copy",children:"Wire up a Slack incoming webhook or any HTTPS endpoint to receive alerts when devices go offline, new devices register, or sessions fail. Configure your first webhook below."})]}):e.jsx("div",{className:"webhook-list-grid",children:g.map(s=>{const t=(()=>{try{return JSON.parse(s.events)}catch{return[]}})();return e.jsxs("div",{className:"webhook-row-card",children:[e.jsxs("div",{className:"webhook-row-card__header",children:[e.jsx("span",{className:`pill-chip ${s.payloadTemplate?"pill-chip--admin":"pill-chip--scope"}`,children:s.payloadTemplate?"CUSTOM":"SLACK"}),e.jsx("span",{className:"webhook-row-card__url",title:s.url,children:s.url}),e.jsxs("button",{type:"button",className:"row-action row-action--danger",onClick:()=>E(s.id,s.url),"aria-label":`Remove webhook for ${s.url}`,children:[e.jsx(O,{size:13}),e.jsx("span",{children:"Remove"})]})]}),e.jsx("div",{className:"webhook-row-card__events",children:t.length===0?e.jsx("span",{className:"webhook-row-card__no-events",children:"No triggers"}):t.map(a=>{const i=C.find(F=>F.id===a),W=(i==null?void 0:i.icon)??u;return e.jsxs("span",{className:`event-pill event-pill--${(i==null?void 0:i.tone)||"gray"}`,children:[e.jsx(W,{size:11}),(i==null?void 0:i.label)||a]},a)})})]},s.id)})}),e.jsxs("div",{className:"add-webhook-card",children:[e.jsxs("div",{className:"add-webhook-card__header",children:[e.jsx(x,{size:14}),e.jsx("span",{children:"Add a new webhook"})]}),e.jsxs("div",{className:"add-webhook-card__body",children:[e.jsx(S,{label:"Webhook URL",description:"The endpoint we POST webhook events to.",htmlFor:"webhook-url",children:e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{id:"webhook-url",type:"url",placeholder:"https://hooks.slack.com/services/...",value:l,onChange:s=>j(s.target.value)})})}),e.jsx(S,{label:"Trigger events",children:e.jsx("div",{className:"event-toggle-grid",children:C.map(s=>{const t=d.includes(s.id),a=s.icon;return e.jsxs("button",{type:"button",role:"checkbox","aria-checked":t,className:`event-toggle event-toggle--${s.tone} ${t?"is-selected":""}`,onClick:()=>$(s.id),children:[e.jsx(a,{size:14,className:"event-toggle__lead"}),e.jsx("span",{children:s.label}),t&&e.jsx(B,{size:14,className:"event-toggle__check"})]},s.id)})})}),e.jsxs("div",{className:"template-section",children:[e.jsxs("button",{type:"button",className:"template-section__toggle",onClick:()=>_(!n),"aria-expanded":n,children:[e.jsx(I,{size:14,className:n?"tone-amber":"tone-dim"}),e.jsx("span",{children:"Use custom payload (optional)"}),e.jsx("span",{className:"template-section__indicator",children:n?"−":"+"})]}),n&&e.jsxs("div",{className:"template-editor",children:[e.jsx("p",{className:"template-editor__hint",children:"Define a JSON or text template. Click a variable below to insert it."}),e.jsx("div",{className:"template-editor__chips",children:X.map(s=>e.jsx("button",{type:"button",className:"template-editor__chip",onClick:()=>R(s),children:`{{${s}}}`},s))}),e.jsx("textarea",{className:"template-editor__textarea",placeholder:'Example: { "text": "Alert: Device {{udid}} is offline!" }',value:y,onChange:s=>m(s.target.value),rows:4})]})]})]}),e.jsxs("div",{className:"add-webhook-card__footer",children:[e.jsxs("button",{type:"button",className:"page-header-action page-header-action--ghost",onClick:L,disabled:!l.trim()||v,children:[v?e.jsx(b,{size:14,className:"animate-spin"}):e.jsx(J,{size:14}),e.jsx("span",{children:"Test payload"})]}),e.jsxs("button",{type:"button",className:"page-header-action",onClick:z,disabled:h||!l.trim(),children:[h?e.jsx(b,{size:14,className:"animate-spin"}):e.jsx(x,{size:14}),e.jsx("span",{children:h?"Saving…":"Save webhook"})]})]})]})]})]})};export{ae as WebhookSettings};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-DbzQAv86.js";/**
1
+ import{c as a}from"./index-BHDlQcd6.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -36,7 +36,7 @@
36
36
  Learn how to configure a non-root public URL by running `npm run build`.
37
37
  -->
38
38
  <title>Xenon</title>
39
- <script type="module" crossorigin src="/xenon/assets/index-DbzQAv86.js"></script>
39
+ <script type="module" crossorigin src="/xenon/assets/index-BHDlQcd6.js"></script>
40
40
  <link rel="stylesheet" crossorigin href="/xenon/assets/index-C7S8Y7Fj.css">
41
41
  </head>
42
42
 
@@ -48,6 +48,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
48
48
  const plugin_1 = require("../../plugin");
49
49
  const express_1 = require("express");
50
50
  const device_store_1 = require("../../data-service/device-store");
51
+ const fillMissingScreenSize_1 = require("../../services/fillMissingScreenSize");
51
52
  const device_managers_1 = require("../../device-managers");
52
53
  const typedi_1 = require("typedi");
53
54
  const logger_1 = __importDefault(require("../../logger"));
@@ -134,6 +135,14 @@ function getDeviceInfo(udid) {
134
135
  return yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
135
136
  });
136
137
  }
138
+ /**
139
+ * The ways into fillMissingScreenSize. A reload restores a tile through the
140
+ * ticket and the stream, never stream/start, so all three fill a missing size.
141
+ */
142
+ const screenSizeDeps = {
143
+ managerFor: (platform) => getDeviceManagerForPlatform(platform),
144
+ updateDevice: (udid, host, patch) => device_store_1.DeviceStoreFactory.getStore().updateDevice(udid, host, patch),
145
+ };
137
146
  function getDeviceManagerForPlatform(platform) {
138
147
  return __awaiter(this, void 0, void 0, function* () {
139
148
  const dfm = typedi_1.Container.get(device_managers_1.XenonManager);
@@ -670,21 +679,9 @@ router.post('/:udid/stream/start', (req, res) => __awaiter(void 0, void 0, void
670
679
  else {
671
680
  mjpegPort = (yield typedi_1.Container.get(AndroidStreamService_1.default).startStream(udid)).mjpegPort;
672
681
  }
673
- // Refresh device info (Lazy loading of dimensions if missing)
674
- try {
675
- const manager = yield getDeviceManagerForPlatform(device.platform);
676
- if (manager &&
677
- manager.getAdditionalDeviceInfo &&
678
- (!device.screenWidth || !device.screenHeight)) {
679
- logger_1.default.info(`Fetching missing dimensions for ${udid} on stream start`);
680
- const additionalInfo = yield manager.getAdditionalDeviceInfo(device);
681
- if (additionalInfo && Object.keys(additionalInfo).length > 0) {
682
- yield device_store_1.DeviceStoreFactory.getStore().updateDevice(udid, device.host, additionalInfo);
683
- }
684
- }
685
- }
686
- catch (e) {
687
- logger_1.default.warn(`Non-critical: Failed to fetch additional device info during stream start: ${e}`);
682
+ // Lazy-load the screen size if missing: tiles need it to accept taps.
683
+ if (yield (0, fillMissingScreenSize_1.fillMissingScreenSize)(device, screenSizeDeps)) {
684
+ logger_1.default.info(`Fetched missing dimensions for ${udid} on stream start`);
688
685
  }
689
686
  // Principal Insight: Concurrency Protection
690
687
  // Mark device as "Busy" so automation sessions don't pick it up.
@@ -747,6 +744,10 @@ router.post('/:udid/stream/ticket', (req, res) => __awaiter(void 0, void 0, void
747
744
  apiKeyId: actor.apiKeyId,
748
745
  });
749
746
  res.json({ ticket, expiresIn: 60 });
747
+ // The H.264 preview attaches here on a reload; see screenSizeDeps.
748
+ void getDeviceInfo(req.params.udid)
749
+ .then((device) => device && (0, fillMissingScreenSize_1.fillMissingScreenSize)(device, screenSizeDeps))
750
+ .catch(() => undefined);
750
751
  }));
751
752
  /**
752
753
  * Stop stream endpoint
@@ -889,6 +890,8 @@ router.get('/:udid/stream', (req, res) => __awaiter(void 0, void 0, void 0, func
889
890
  const device = yield getDeviceInfo(udid);
890
891
  if (!device)
891
892
  return res.status(404).send('Device not found');
893
+ // The MJPEG preview attaches here on a reload; see screenSizeDeps.
894
+ void (0, fillMissingScreenSize_1.fillMissingScreenSize)(device, screenSizeDeps);
892
895
  let mjpegPort = device.mjpegServerPort;
893
896
  // For iOS devices, try to auto-start the stream if not available
894
897
  if (device.platform === 'ios' || device.platform === 'tvos') {
@@ -59,6 +59,7 @@ const prisma_1 = require("../../prisma");
59
59
  const recordingRequests_1 = require("./recordingRequests");
60
60
  const annotationImage_1 = require("../../services/recording/annotationImage");
61
61
  const activeRecordings_1 = require("../../services/recording/activeRecordings");
62
+ const recordingTiming_1 = require("../../services/recording/recordingTiming");
62
63
  const device_store_1 = require("../../data-service/device-store");
63
64
  const logger_1 = __importDefault(require("../../logger"));
64
65
  // Phase 4A: a recording group is visible if at least one of its rows runs on
@@ -236,7 +237,7 @@ router.get('/recordings/active', (req, res) => __awaiter(void 0, void 0, void 0,
236
237
  const d = yield device_store_1.DeviceStoreFactory.getStore().findDevice({ udid });
237
238
  locks.set(udid, (_a = d === null || d === void 0 ? void 0 : d.session_id) !== null && _a !== void 0 ? _a : null);
238
239
  }
239
- const groups = (0, activeRecordings_1.selectOwnActiveGroups)(rows, (u) => locks.get(u), actor).map((g) => (Object.assign(Object.assign({}, g), { compositeEnabled: fs.existsSync((0, RecordingOrchestrator_1.compositeOutputPath)(g.groupId)) })));
240
+ const groups = (0, activeRecordings_1.selectOwnActiveGroups)(rows, (u) => locks.get(u), actor, (r) => { var _a; return (r.file_path ? (_a = (0, recordingTiming_1.readRecordingTiming)(r.file_path)) === null || _a === void 0 ? void 0 : _a.groupT0Ms : undefined); }).map((g) => (Object.assign(Object.assign({}, g), { compositeEnabled: fs.existsSync((0, RecordingOrchestrator_1.compositeOutputPath)(g.groupId)) })));
240
241
  return res.json({ serverNow: Date.now(), groups });
241
242
  }
242
243
  catch (e) {
@@ -287,8 +288,9 @@ router.get('/recordings/:groupId/composite.mp4', (req, res) => __awaiter(void 0,
287
288
  if (!(yield isGroupVisibleToAuth(req.params.groupId, auth === null || auth === void 0 ? void 0 : auth.teamIds))) {
288
289
  return res.status(404).json({ error: 'composite_not_found' });
289
290
  }
290
- const compositePath = (0, RecordingOrchestrator_1.compositeOutputPath)(req.params.groupId);
291
- if (!fs.existsSync(compositePath)) {
291
+ // With the devices' marks burned in when that works, otherwise raw.
292
+ const compositePath = yield typedi_1.Container.get(proof_bundle_1.ProofBundleService).resolveCompositeFile(req.params.groupId);
293
+ if (!compositePath) {
292
294
  return res.status(404).json({ error: 'composite_not_found' });
293
295
  }
294
296
  res.setHeader('Content-Type', 'video/mp4');