apitestcenter 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD-PARTY-NOTICES-UI.txt +1 -1
- package/dist/THIRD-PARTY-NOTICES.txt +105 -1
- package/dist/cli.mjs +5213 -227
- package/dist/ui/404/index.html +1 -1
- package/dist/ui/404.html +1 -1
- package/dist/ui/__next.__PAGE__.txt +15 -14
- package/dist/ui/__next._full.txt +13 -12
- package/dist/ui/__next._tree.txt +2 -2
- package/dist/ui/_next/static/chunks/01tk3yrimgagm.js +2 -0
- package/dist/ui/_next/static/chunks/{3ad2zh-p8_xhv.js → 0gfm4dc7bjzy1.js} +3 -3
- package/dist/ui/_next/static/chunks/{2idsynwg5lkk7.js → 115l_disvbdxa.js} +1 -1
- package/dist/ui/_next/static/chunks/{13xdai3-mkg7c.js → 199g-q88qtg_i.js} +549 -134
- package/dist/ui/_next/static/chunks/1ax1v4312l2w9.js +1 -0
- package/dist/ui/_next/static/chunks/1ffsp9e_ihf8c.js +19 -0
- package/dist/ui/_next/static/chunks/239j59-_u852y.css +3 -0
- package/dist/ui/_next/static/chunks/2xiq0vxqp_rsu.js +1 -0
- package/dist/ui/_next/static/chunks/{02myfb0nlri-z.js → 3jywp_61ylz0z.js} +1 -1
- package/dist/ui/_next/static/media/icon.1oy4kb0w-k_2v.png +0 -0
- package/dist/ui/_not-found/__next._full.txt +12 -11
- package/dist/ui/_not-found/__next._not-found.__PAGE__.txt +17 -16
- package/dist/ui/_not-found/__next._tree.txt +2 -2
- package/dist/ui/_not-found/index.html +1 -1
- package/dist/ui/_not-found/index.txt +12 -11
- package/dist/ui/compare/__next._full.txt +13 -12
- package/dist/ui/compare/__next._tree.txt +2 -2
- package/dist/ui/compare/__next.compare.__PAGE__.txt +18 -17
- package/dist/ui/compare/index.html +1 -1
- package/dist/ui/compare/index.txt +13 -12
- package/dist/ui/history/__next._full.txt +14 -13
- package/dist/ui/history/__next._tree.txt +2 -2
- package/dist/ui/history/__next.history.__PAGE__.txt +19 -18
- package/dist/ui/history/index.html +1 -1
- package/dist/ui/history/index.txt +14 -13
- package/dist/ui/icon.png +0 -0
- package/dist/ui/index.html +1 -1
- package/dist/ui/index.txt +13 -12
- package/dist/ui/preview/__next._full.txt +14 -13
- package/dist/ui/preview/__next._tree.txt +2 -2
- package/dist/ui/preview/__next.preview.__PAGE__.txt +19 -18
- package/dist/ui/preview/index.html +1 -1
- package/dist/ui/preview/index.txt +14 -13
- package/dist/ui/suite/__next._full.txt +13 -12
- package/dist/ui/suite/__next._tree.txt +2 -2
- package/dist/ui/suite/__next.suite.__PAGE__.txt +18 -17
- package/dist/ui/suite/index.html +1 -1
- package/dist/ui/suite/index.txt +13 -12
- package/dist/ui/tests/__next._full.txt +12 -11
- package/dist/ui/tests/__next._tree.txt +2 -2
- package/dist/ui/tests/__next.tests.__PAGE__.txt +17 -16
- package/dist/ui/tests/index.html +1 -1
- package/dist/ui/tests/index.txt +12 -11
- package/package.json +1 -1
- package/dist/ui/_next/static/chunks/2i7s5kuurlkr6.css +0 -3
- package/dist/ui/_next/static/chunks/2nxnxp6p-40kg.js +0 -1
- package/dist/ui/_next/static/chunks/2od90u8fd_sla.js +0 -18
- package/dist/ui/_next/static/chunks/36lx6-56-udwb.js +0 -2
- package/dist/ui/_next/static/chunks/3qxq1d9u_kohv.js +0 -1
- /package/dist/ui/_next/static/{C6zMFzDkVqF8IXny_v7JK → XGxojrJs_nD9y7xtRMI1q}/_buildManifest.js +0 -0
- /package/dist/ui/_next/static/{C6zMFzDkVqF8IXny_v7JK → XGxojrJs_nD9y7xtRMI1q}/_clientMiddlewareManifest.js +0 -0
- /package/dist/ui/_next/static/{C6zMFzDkVqF8IXny_v7JK → XGxojrJs_nD9y7xtRMI1q}/_ssgManifest.js +0 -0
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
${u}
|
|
49
49
|
<Relationship Id="rId${c.length+1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
50
50
|
</Relationships>`,"xl/styles.xml":k};for(let e of c)x[`xl/worksheets/sheet${e.id}.xml`]=e.xml;return new Blob([(0,w.zipStore)(x)],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"})}async function S(e,t,s){let a=N().replace(/\.xlsx$/,""),r={[`${a}.xlsx`]:new Uint8Array(await (await I(e,t,s)).arrayBuffer()),[`${a}.json`]:(0,b.exportCompareJson)(e,t)};for(let t of e){if(!t.probe)continue;let e=t.title.replace(/[^\w.-]+/g,"-").slice(0,40)||t.id.slice(0,8);r[`${e}-probe.json`]=(0,b.exportProbeJson)(t.probe)}return{base:a,blob:(0,w.zipStore)(r)}}var C=e.i(15060),$=e.i(88153);function A({apis:e,mode:s,probes:a}){var n,l,i;let o,d,c,p,m,{slo:u}=(0,$.useSlo)(),x=N(),v=x.replace(/\.xlsx$/,""),j=Object.entries(a).filter(e=>!!e[1]),[k,F]=(0,h.useState)(!1),[R,E]=(0,h.useState)(null),[T,z]=(0,h.useState)(null);if(0===e.length)return null;let D=(n=e,l=s,i=u,o=n.map(e=>e.title),d=`Compare: ${o.join(" vs ")}`.slice(0,120),c=n.length>=2?(0,f.compareKpis)(n.map(e=>e.report)):[],p=[`APITestCenter compare (${l})`,"",...n.map(e=>`- ${e.title}: ${e.report.testName} [${e.report.status}]`),"",...c.map(e=>{let t=e.values.map((t,s)=>{var a,r;return`${o[s]}=${a=t,"ms"===(r=e.unit)?`${Math.round(a)}ms`:"rps"===r?`${a.toFixed(1)}/s`:"pct"===r?`${(100*a).toFixed(2)}%`:String(Math.round(a))}`}).join(", "),s=null==e.winner?"tie":o[e.winner]??"tie";return`${e.label}: ${t} (best: ${s})`})],i&&p.push("",`Performance targets: p95 ${i.p95Ms} ms, errors ${(100*i.errorRate).toFixed(1)}%`),p.push("","Attach the Excel or Zip export from APITestCenter."),{subject:d,body:m=p.join("\n"),mailto:`mailto:?subject=${encodeURIComponent(d)}&body=${encodeURIComponent(m)}`,gmail:`https://mail.google.com/mail/?view=cm&fs=1&tf=1&su=${encodeURIComponent(d)}&body=${encodeURIComponent(m)}`,outlook:`https://outlook.office.com/mail/deeplink/compose?subject=${encodeURIComponent(d)}&body=${encodeURIComponent(m)}`});async function O(){z(null),F(!1);try{let{base:t,blob:a}=await S(e,s,u),r={files:[new File([a],`${t}-export.zip`,{type:"application/zip"})],title:D.subject,text:D.body};if(navigator.canShare?.({files:r.files}))return void await navigator.share(r);E("mailto")}catch(e){if(e instanceof DOMException&&"AbortError"===e.name)return;E("mailto"),z(e instanceof Error?e.message:"Share failed")}}return(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,t.jsxs)("span",{className:"mr-1 flex items-center gap-1.5 text-xs text-mute",children:[(0,t.jsx)(g.Download,{size:14}),"Export"]}),(0,t.jsx)("button",{type:"button",disabled:e.length<2,className:"rounded-md border border-line bg-panel-2 px-2.5 py-1 text-xs text-ink hover:border-accent hover:text-accent disabled:opacity-40",onClick:()=>void I(e,s,u).then(e=>(0,C.downloadBlob)(x,e)),children:"Excel"}),(0,t.jsx)("button",{type:"button",className:"rounded-md border border-line bg-panel-2 px-2.5 py-1 text-xs text-ink hover:border-accent hover:text-accent",onClick:()=>(0,C.downloadText)(`${v}.json`,(0,b.exportCompareJson)(e,s),"application/json"),children:"JSON"}),(0,t.jsx)("button",{type:"button",disabled:0===j.length,className:"rounded-md border border-line bg-panel-2 px-2.5 py-1 text-xs text-ink hover:border-accent hover:text-accent disabled:opacity-40",onClick:()=>{let t={};for(let[s,a]of j){let r=e.find(e=>e.id===s),n=(r?.title??s).replace(/[^\w.-]+/g,"-").slice(0,40);t[`${n}-probe.json`]=(0,b.exportProbeJson)(a)}(0,C.downloadBlob)(`${v}-probes.zip`,(0,w.zipStore)(t))},children:"Response JSONs"}),(0,t.jsxs)("button",{type:"button",className:"inline-flex items-center gap-1 rounded-md border border-line bg-panel-2 px-2.5 py-1 text-xs text-ink hover:border-accent hover:text-accent",onClick:()=>void S(e,s,u).then(({base:e,blob:t})=>(0,C.downloadBlob)(`${e}-export.zip`,t)),children:[(0,t.jsx)(g.Download,{size:12}),"Zip"]}),(0,t.jsxs)("div",{className:"relative",children:[(0,t.jsxs)("button",{type:"button",onClick:()=>F(e=>!e),className:"inline-flex items-center gap-1 rounded-md border border-line bg-panel-2 px-2.5 py-1 text-xs text-ink hover:border-accent hover:text-accent",children:[(0,t.jsx)(y.Share2,{size:12}),"Share",(0,t.jsx)(r.ChevronDown,{size:12})]}),k?(0,t.jsxs)("div",{className:"absolute right-0 z-20 mt-1 w-56 rounded-md border border-line bg-panel p-1 shadow-lg",children:[(0,t.jsx)("button",{type:"button",className:"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-ink hover:bg-panel-2",onClick:()=>{F(!1),E("gmail")},children:"Gmail"}),(0,t.jsx)("button",{type:"button",className:"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-ink hover:bg-panel-2",onClick:()=>{F(!1),E("outlook")},children:"Outlook on the web"}),(0,t.jsx)("button",{type:"button",className:"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-ink hover:bg-panel-2",onClick:()=>{F(!1),E("mailto")},children:"Default mail app"}),(0,t.jsx)("button",{type:"button",className:"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-ink hover:bg-panel-2",onClick:()=>void O(),children:"System share"})]}):null]})]}),T?(0,t.jsx)("p",{className:"max-w-md text-xs text-mute",children:T}):null,R?(0,t.jsx)(P,{apis:e,mode:s,target:R,links:D,onClose:()=>E(null)}):null]})}function P({apis:e,mode:s,target:a,links:r,onClose:n}){let{slo:l}=(0,$.useSlo)(),i={gmail:"Gmail",outlook:"Outlook on the web",mailto:"default mail app"},o="gmail"===a?r.gmail:"outlook"===a?r.outlook:r.mailto;return(0,t.jsx)("div",{className:"fixed inset-0 z-50 grid place-items-center bg-canvas/70 p-4",role:"presentation",onClick:n,children:(0,t.jsxs)("div",{role:"dialog","aria-modal":"true",className:"w-full max-w-md rounded-lg border border-line bg-panel p-4 shadow-xl",onClick:e=>e.stopPropagation(),children:[(0,t.jsxs)("h2",{className:"text-sm font-medium text-ink",children:["Share via ",i[a]]}),(0,t.jsx)("p",{className:"mt-2 text-xs leading-5 text-mute",children:"Download the compare package, then attach it in the compose window. Browsers cannot attach files for you."}),(0,t.jsxs)("div",{className:"mt-4 flex flex-wrap gap-2",children:[(0,t.jsx)("button",{type:"button",className:"rounded-md border border-line bg-panel-2 px-2.5 py-1.5 text-xs text-ink",onClick:()=>void I(e,s,l).then(e=>(0,C.downloadBlob)(N(),e)),children:"Download Excel"}),(0,t.jsx)("button",{type:"button",className:"rounded-md border border-line bg-panel-2 px-2.5 py-1.5 text-xs text-ink",onClick:()=>void S(e,s,l).then(({base:e,blob:t})=>(0,C.downloadBlob)(`${e}-export.zip`,t)),children:"Download Zip"}),(0,t.jsxs)("a",{href:o,target:"_blank",rel:"noreferrer",className:"rounded-md bg-accent px-2.5 py-1.5 text-xs font-medium text-canvas",onClick:n,children:["Open ",i[a]]}),(0,t.jsx)("button",{type:"button",className:"rounded-md px-2.5 py-1.5 text-xs text-mute",onClick:n,children:"Close"})]})]})})}var F=e.i(55658),R=e.i(53012),E=e.i(45404),T=e.i(29724),z=e.i(88231);function D({items:e,liveTabs:s,running:a,source:r,mode:l,slo:i,title:o,actions:d,status:c,exportBar:p}){let[m,u]=(0,h.useState)("all"),x=(0,h.useRef)(null),b=e.map((e,t)=>(0,f.compareSeriesLabel)(t,e.title)),g=a||!e.some(e=>e.tabId===m)?"all":m,y=e.filter(e=>!s.has(e.tabId)),v=!a&&y.length>=2?(0,F.compareFairness)(y.map(e=>e.report),y.map(t=>b[e.indexOf(t)])):null;return(0,t.jsxs)("div",{ref:x,className:"min-h-0 flex-1 overflow-y-auto",children:[(0,t.jsxs)("header",{className:"sticky top-0 z-10 border-b border-line bg-canvas/95 px-4 pt-3 backdrop-blur lg:px-6",children:[(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-x-4 gap-y-2",children:[(0,t.jsx)("h2",{className:"text-base font-medium text-ink",children:o}),(0,t.jsx)("div",{className:"ml-auto flex flex-wrap items-center gap-2",children:d})]}),(0,t.jsx)("div",{role:"tablist","aria-label":"Comparison or one run",className:"-mb-px mt-3 flex gap-1 overflow-x-auto",children:[{id:"all",label:"Comparison",color:null},...e.map((e,t)=>({id:e.tabId,label:b[t],color:(0,z.seriesColor)(t)}))].map(e=>{let r=g===e.id,l=s.has(e.id);return(0,t.jsxs)("button",{type:"button",role:"tab","aria-selected":r,disabled:a&&"all"!==e.id,title:a&&"all"!==e.id?"Each run's own report opens when the compare has finished":e.label,onClick:()=>{u(e.id),x.current?.scrollTo({top:0})},className:`flex max-w-[18rem] shrink-0 items-center gap-2 rounded-t-md border border-b-0 px-3 py-2 text-sm disabled:opacity-50 ${r?"border-line bg-panel font-medium text-ink":"border-transparent text-mute hover:bg-panel-2 hover:text-ink"}`,children:[e.color?(0,t.jsx)("span",{className:"h-2 w-2 shrink-0 rounded-full",style:{background:e.color},"aria-hidden":!0}):null,(0,t.jsx)("span",{className:"truncate",children:e.label}),l?(0,t.jsx)(n.Loader2,{size:12,className:"shrink-0 animate-spin text-accent","aria-label":"running"}):null]},e.id)})})]}),(0,t.jsx)("div",{className:"space-y-4 px-4 py-4 lg:px-6",children:"all"===g?(0,t.jsxs)(t.Fragment,{children:[c,p,v?.unfair?(0,t.jsxs)("p",{role:"note",className:"flex items-start gap-2 rounded-md border border-warn/40 bg-warn/10 px-3 py-2 text-sm text-ink",children:[(0,t.jsx)(R.AlertTriangle,{size:16,className:"mt-0.5 shrink-0 text-warn","aria-hidden":!0}),(0,t.jsxs)("span",{children:[v.unfair," Compare runs with the same load and length to say which API is faster."]})]}):null,(0,t.jsxs)("p",{className:"text-xs text-mute",children:["saved"===r?`Saved runs: ${e.map((e,t)=>{let s;return`${b[t]} ${Number.isNaN((s=new Date(e.report.startedAt)).getTime())?"":s.toLocaleString(void 0,{day:"numeric",month:"short",hour:"numeric",minute:"2-digit"})}`}).join(" · ")}.`:"parallel"===l?"Ran at the same time — the runs shared this computer's CPU and network, so treat the numbers as directional.":"Ran one after another with the same load.",v&&!v.unfair&&v.caveats.length?` Note: ${v.caveats.join("; ")}.`:""]}),e.length>=2?(0,t.jsx)(E.Dashboard,{scope:"compare",data:{reports:e.map(e=>e.report),titles:e.map((e,t)=>b[t]+(s.has(e.tabId)?" · live":"")),slo:i,live:a}}):(0,t.jsx)("p",{className:"rounded-lg border border-dashed border-line px-6 py-14 text-center text-sm text-mute",children:a?"The overlay appears when the first API is under way…":"The overlay needs at least two finished runs."})]}):(0,t.jsx)(T.ReportView,{report:e.find(e=>e.tabId===g).report,source:"engine"})})]})}var O=e.i(2385),q=e.i(19656);function L(e){let t=Math.max(0,Math.ceil(e/1e3)),s=Math.floor(t/60),a=t%60;return s>0?`${s}:${String(a).padStart(2,"0")}`:`${a}s`}function M(e){let t=Math.max(0,Math.round(e/1e3));if(t<60)return`${t}s`;let s=Math.floor(t/60),a=t%60;return a?`${s}m ${a}s`:`${s}m`}function U({mode:e,phase:s,liveRuns:a,startedAt:r,endedAt:n}){let[l,i]=(0,h.useState)(()=>Date.now()),o="running"===s||"probing"===s||"between"===s;return((0,h.useEffect)(()=>{if(!o)return;let e=setInterval(()=>i(Date.now()),250);return()=>clearInterval(e)},[o]),"idle"===s)?null:"probing"===s?(0,t.jsxs)("div",{className:"rounded-md border border-accent/30 bg-accent-dim px-3 py-2",children:[(0,t.jsx)("div",{className:"text-xs tracking-wide text-accent uppercase",children:"Checking APIs"}),(0,t.jsx)("p",{className:"mt-0.5 text-xs text-mute",children:"Probing each API before load. This can take a few seconds."})]}):"between"===s?(0,t.jsxs)("div",{className:"rounded-md border border-accent/30 bg-accent-dim px-3 py-2",children:[(0,t.jsx)("div",{className:"text-xs tracking-wide text-accent uppercase",children:"Preparing next API"}),(0,t.jsx)("p",{className:"mt-0.5 text-xs text-mute",children:"Processing the last run and starting the next one. Sequence compares take a moment between APIs."}),r?(0,t.jsxs)("p",{className:"mt-1 font-mono text-sm text-ink",children:["Elapsed ",M(l-Date.parse(r))]}):null]}):"done"===s&&r&&n?(0,t.jsxs)("div",{className:"rounded-md border border-line bg-panel-2 px-3 py-2",children:[(0,t.jsx)("div",{className:"text-xs tracking-wide text-mute uppercase",children:"Overall time taken"}),(0,t.jsx)("p",{className:"mt-0.5 font-mono text-lg text-ink",children:(0,q.formatDuration)(r,n)}),(0,t.jsxs)("p",{className:"mt-0.5 text-xs text-mute",children:["Wall clock for the whole compare (",e,"), including probes and gaps between runs."]})]}):"running"!==s||0===a.length?null:(0,t.jsxs)("div",{className:"space-y-2",children:[a.map(e=>{let s=(0,O.expectedLoadMs)(e.report),a=Math.max(0,l-Date.parse(e.report.startedAt)),r=Math.max(0,s-a),n="running"===e.status&&0===r;return(0,t.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3 rounded-md border border-accent/30 bg-accent-dim px-3 py-2",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"text-xs tracking-wide text-accent uppercase",children:["queued"===e.status?"Starting":n?"Finishing":"Load running"," · ",e.title]}),(0,t.jsx)("p",{className:"mt-0.5 text-xs text-mute",children:n?"Clock hit zero. k6 is letting in-flight requests finish.":`${L(a)} elapsed`})]}),(0,t.jsx)("div",{className:"font-mono text-2xl tabular text-ink",children:"queued"===e.status?"…":n?"—":L(r)})]},e.report.id)}),r?(0,t.jsxs)("p",{className:"text-xs text-mute",children:["Compare elapsed ",M(l-Date.parse(r))]}):null]})}var B=e.i(22543),J=e.i(63343),V=e.i(44794),H=e.i(31753),K=e.i(60566),_=e.i(39170),W=e.i(28867),X=e.i(56396);e.i(42674);var G=e.i(5305);let Z="apitestcenter-compare-draft";function Q(){return globalThis.crypto?.randomUUID?.()??`tab-${Date.now()}-${Math.random().toString(16).slice(2)}`}function Y(e){return{...(0,G.defaultRequestDraft)(),id:Q(),title:e}}function ee(){let e=Y("API 1");return{tabs:[e,Y("API 2")],activeId:e.id,load:{...G.DEFAULT_LOAD}}}function et(e,t){let s=e&&"object"==typeof e?e:{},a="string"==typeof s.title&&s.title.trim()||"string"==typeof s.label&&s.label.trim()||t;return{...(0,G.requestFromUnknown)(e),id:"string"==typeof s.id&&s.id?s.id:Q(),title:a,collection:"string"==typeof s.collection&&s.collection?s.collection:void 0}}var es=e.i(21206),ea=e.i(20054),er=e.i(68228),en=e.i(65638),el=e.i(6278),ei=e.i(46679),eo=e.i(65238),ed=e.i(727),ec=e.i(59180),ep=e.i(2603);let em=new Set(["title"]);function eu({tabs:e,activeId:s,probes:a,liveByTab:r,reports:l,running:i,onSelect:d,onRename:c,onClose:p,onAdd:u}){let[x,f]=(0,h.useState)(null);return(0,t.jsxs)("div",{className:"flex items-end gap-1 overflow-x-auto border-b border-line bg-canvas px-2 pt-2",role:"tablist","aria-label":"APIs to compare",children:[e.map((o,u)=>{let h=o.id===s,b=a[o.id],g=!!r[o.id],y=l.some(e=>e.tabId===o.id);return(0,t.jsxs)("div",{className:`group flex max-w-[15rem] shrink-0 items-center gap-1.5 rounded-t-md border border-b-0 pr-1 pl-2.5 text-sm ${h?"-mb-px border-line bg-panel text-ink":"border-transparent text-mute hover:bg-panel-2 hover:text-ink"}`,children:[(0,t.jsx)("span",{className:"h-2 w-2 shrink-0 rounded-full",style:{background:(0,z.seriesColor)(u)},"aria-hidden":!0}),x===o.id?(0,t.jsx)("input",{autoFocus:!0,defaultValue:o.title,"aria-label":"API name",onBlur:e=>{c(o.id,e.target.value.trim()||`API ${u+1}`),f(null)},onKeyDown:e=>{"Enter"===e.key&&e.target.blur(),"Escape"===e.key&&f(null)},className:"w-32 bg-transparent py-2 outline-none"}):(0,t.jsxs)("button",{type:"button",role:"tab","aria-selected":h,title:"Double-click to rename",onClick:()=>d(o.id),onDoubleClick:()=>f(o.id),className:"flex min-w-0 items-center gap-1.5 py-2",children:[(0,t.jsx)("span",{className:"truncate",children:o.title||"Untitled"}),g?(0,t.jsx)(n.Loader2,{size:12,className:"shrink-0 animate-spin text-accent","aria-label":"running"}):b?(0,t.jsx)("span",{className:`shrink-0 font-mono text-xs ${(0,es.isSuccess)(b)?"text-ok":"text-bad"}`,children:b.status||"ERR"}):y?(0,t.jsx)("span",{className:"shrink-0 text-xs text-ok",children:"done"}):null]}),e.length>2?(0,t.jsx)("button",{type:"button","aria-label":`Close ${o.title}`,disabled:i,onClick:()=>p(o.id),className:"rounded p-0.5 text-mute opacity-0 group-hover:opacity-100 hover:bg-panel-2 hover:text-bad focus:opacity-100 disabled:opacity-0",children:(0,t.jsx)(m.X,{size:12})}):null]},o.id)}),(0,t.jsxs)("button",{type:"button",onClick:u,disabled:e.length>=6||i,title:e.length>=6?"Up to 6 APIs":"Add an API",className:"mb-1 ml-1 inline-flex shrink-0 items-center gap-1 rounded-md px-2 py-1.5 text-xs text-mute hover:bg-panel-2 hover:text-ink disabled:opacity-40",children:[(0,t.jsx)(o.Plus,{size:14}),"Add API"]})]})}function ex({contract:e,blocked:s,skipped:a,busy:r,onProceed:n}){if(!e)return null;if("pass"===e.level)return(0,t.jsx)("p",{className:"rounded-md border border-ok/40 bg-ok/10 px-3 py-2 text-sm text-ok",children:"✓ Every API returned the same response structure."});let l=e.issues.filter(e=>!/Need at least two/.test(e.message));return(0,t.jsxs)("div",{role:s?"alert":void 0,className:"rounded-md border border-warn/40 bg-warn/10 px-3 py-2.5",children:[(0,t.jsx)("p",{className:"text-sm font-medium text-warn",children:s?"Compare not started — responses differ.":a?"Structures differ (allowed for this compare).":"Responses differ."}),l.length?(0,t.jsx)("ul",{className:"mt-1 list-disc space-y-0.5 pl-5 text-sm text-ink",children:l.map((e,s)=>(0,t.jsx)("li",{children:e.message},s))}):null,s?(0,t.jsx)("div",{className:"mt-2 flex flex-wrap gap-2",children:(0,t.jsx)("button",{type:"button",className:"btn btn-secondary py-1.5 text-xs",disabled:r,onClick:n,children:"Compare anyway"})}):null]})}function eh({entries:e,disabled:s,onPick:a}){let[n,l]=(0,h.useState)(!1),i=(0,h.useRef)(null);return(0,h.useEffect)(()=>{if(!n)return;let e=e=>!i.current?.contains(e.target)&&l(!1),t=e=>"Escape"===e.key&&l(!1);return document.addEventListener("pointerdown",e),document.addEventListener("keydown",t),()=>{document.removeEventListener("pointerdown",e),document.removeEventListener("keydown",t)}},[n]),(0,t.jsxs)("div",{ref:i,className:"relative",children:[(0,t.jsxs)("button",{type:"button",disabled:s,"aria-haspopup":"listbox","aria-expanded":n,onClick:()=>l(e=>!e),className:"inline-flex items-center gap-1 rounded-md border border-line px-2 py-1 text-xs text-mute hover:text-ink disabled:opacity-40",children:["Past compares",(0,t.jsx)(r.ChevronDown,{size:12})]}),n?(0,t.jsx)("ul",{role:"listbox","aria-label":"Past compares",className:"absolute right-0 z-30 mt-1 max-h-80 w-80 overflow-auto rounded-md border border-line bg-panel p-1 shadow-xl",children:e.map(e=>(0,t.jsx)("li",{role:"option","aria-selected":!1,children:(0,t.jsxs)("button",{type:"button",onClick:()=>{a(e),l(!1)},className:"flex w-full items-center justify-between gap-2 rounded px-2 py-1.5 text-left text-xs text-ink hover:bg-panel-2",children:[(0,t.jsx)("span",{className:"min-w-0 truncate",children:e.name}),(0,t.jsxs)("span",{className:"shrink-0 text-mute",children:[e.titles.length," APIs"]})]})},e.id))}):null]})}function ef({children:e}){return(0,t.jsx)("div",{className:"flex flex-1 flex-col items-center justify-center rounded-lg border border-dashed border-line px-6 py-14 text-center text-sm text-mute",children:(0,t.jsx)("div",{className:"max-w-md",children:e})})}function eb(e,t){return function(e){let t=[];if(e.length<2)return{level:"fail",issues:[{level:"fail",message:"Need at least two APIs to compare"}]};for(let a of e){var s;a.probe.ok&&(s=a.probe.status)>=200&&s<300||t.push({level:"fail",message:`${a.label}: probe was ${a.probe.status||"ERR"}, not 2xx`}),a.probe.truncated&&t.push({level:"fail",message:`${a.label}: body was truncated, so the structure cannot be proven`})}let a=e.map(e=>({...e,structure:function(e){var t;let s,a=function(e,t){if(!e.trim())return"empty";if(t.toLowerCase().includes("json"))return"json";try{return JSON.parse(e),"json"}catch{return"text"}}(e.body,(s=Object.keys(t=e.headers).find(e=>"content-type"===e.toLowerCase()))?t[s]:"");if("empty"===a)return"empty";if("text"===a)return`text:${e.body}`;try{return`json:${JSON.stringify(function e(t){if(null===t)return"null";if(Array.isArray(t))return{type:"array",item:t.length>0?e(t[0]):"unknown"};if("object"==typeof t){let s={};for(let a of Object.keys(t).sort())s[a]=e(t[a]);return{type:"object",fields:s}}return typeof t}(JSON.parse(e.body)))}`}catch{return null}}(e.probe)}));if(a.some(e=>null==e.structure))t.push({level:"fail",message:"Could not parse one or more JSON bodies"});else{let e=a[0];if(e)for(let s of a.slice(1))s.structure!==e.structure&&t.push({level:"fail",message:`${s.label} response structure does not match ${e.label}`})}return{level:t.some(e=>"fail"===e.level)?"fail":t.some(e=>"warn"===e.level)?"warn":"pass",issues:t}}(e.map(e=>t[e.id]?{label:e.title||e.id,probe:t[e.id]}:null).filter(e=>!!e))}e.s(["ComparePage",0,function(){let e=(0,x.useSearchParams)(),s=(0,x.useRouter)(),r=(0,X.useToast)(),o=(0,_.useConfirm)(),f=(0,ea.useEngineHealth)(),b=(0,ep.useModKey)(),{slo:g}=(0,$.useSlo)(),{vars:y,activeEnv:v}=(0,el.useWorkspace)(),w=(0,h.useRef)(null),[j,k]=(0,h.useState)(ee),[N,I]=(0,h.useState)(!1),[S,C]=(0,h.useState)(!1),[P,F]=(0,h.useState)(null),[R,E]=(0,h.useState)({}),[T,z]=(0,h.useState)(null),[O,q]=(0,h.useState)(!1),[L,M]=(0,h.useState)("sequence"),[Q,eg]=(0,h.useState)(!1),[ey,ev]=(0,h.useState)(!1),[ew,ej]=(0,h.useState)(!1),[ek,eN]=(0,h.useState)(!1),[eI,eS]=(0,h.useState)({}),[eC,e$]=(0,h.useState)([]),[eA,eP]=(0,h.useState)(!1),[eF,eR]=(0,h.useState)(!1),{index:eE,refresh:eT}=(0,ec.useHistory)(),ez=eE?.compares??[],[eD,eO]=(0,h.useState)("idle"),[eq,eL]=(0,h.useState)(null),[eM,eU]=(0,h.useState)(null),[eB,eJ]=(0,h.useState)("response"),eV=(0,h.useRef)(!1),eH=(0,h.useRef)(new Set);(0,h.useEffect)(()=>{let t=function(){try{let e=sessionStorage.getItem(Z);if(!e)return null;let t=JSON.parse(e),s=[];if(Array.isArray(t.tabs)&&t.tabs.length>0)s=t.tabs.map((e,t)=>et(e,`API ${t+1}`)).slice(0,6);else{if(!t.a||!t.b)return null;s=[et(t.a,"API 1"),et(t.b,"API 2")]}let a="string"==typeof t.activeId&&s.some(e=>e.id===t.activeId)?t.activeId:s[0].id,r=(0,G.loadFromUnknown)(t.load??t);return{tabs:s,activeId:a,load:r}}catch{return null}}();t&&k(t);let s=e.get("compare"),a=(e.get("ids")??[e.get("a"),e.get("b")].filter(Boolean).join(",")).split(",").map(e=>e.trim()).filter(Boolean);s?ec.historyApi.compare(s).then(tt).catch(()=>r("That compare is no longer in History","error")):a.length>=2&&Promise.all(a.map(e=>ec.historyApi.run(e).catch(()=>null))).then(e=>{let t=e.filter(e=>!!e);if(t.length<2)return r("Need at least two of those runs to overlay","error");e$(t.map((e,t)=>({tabId:e.report.id,title:e.meta?.name||e.report.testName||`API ${t+1}`,report:e.report}))),eP(!0),eR(!0),eO("done"),eJ("results")}),I(!0)},[e]),(0,h.useEffect)(()=>{if(!N)return;let e=window.setTimeout(()=>(function(e){try{sessionStorage.setItem(Z,JSON.stringify(e))}catch{}})(j),250);return()=>window.clearTimeout(e)},[j,N]);let eK=j.tabs.find(e=>e.id===j.activeId)??j.tabs[0],e_=j.tabs.filter(G.isReady),eW=e_.length>=2,eX=(0,G.loadProblem)(j.load),eG=(0,h.useMemo)(()=>{let e=new Map(eC.map(e=>[e.tabId,e]));for(let[t,s]of Object.entries(eI)){if(e.has(t))continue;let a=j.tabs.find(e=>e.id===t);e.set(t,{tabId:t,title:a?.title??t,report:s.report})}let t=j.tabs.filter(t=>e.has(t.id)).map(t=>e.get(t.id));return t.length===e.size?t:[...e.values()]},[eC,eI,j.tabs]),eZ=eC.map(e=>({id:e.tabId,title:e.title,report:e.report,probe:R[e.tabId]??null})),eQ=Object.entries(eI).map(([e,t])=>({title:j.tabs.find(t=>t.id===e)?.title||e,status:t.status,report:t.report}));function eY(e,t){let s=j.tabs.find(t=>t.id===e);s&&Object.keys(t).some(e=>!em.has(e)&&t[e]!==s[e])&&R[e]&&(E(({[e]:t,...s})=>s),z(null),q(!1)),k(s=>({...s,tabs:s.tabs.map(s=>s.id===e?t:s)}))}async function e0(e){try{let t=await (0,eo.withFolders)(e,e.collection,y),s=(0,G.resolveDraft)(t.draft,t.vars,v?.name),a=(0,G.buildPayload)(s,(0,G.toLoadConfig)(j.load),(0,G.autoRunName)(s,e.title),(0,G.targetsFromSlo)(g));return await (0,ei.withRunFirst)(a,s,y,v?.name)}catch(t){throw Error(`${e.title||"API"}: ${t instanceof Error?t.message:"invalid request"}`)}}async function e1(){let e;if(!S&&!ew&&(0,G.isReady)(eK)){try{e=await e0(eK)}catch(e){F(e instanceof Error?e.message:"Invalid request");return}C(!0),F(null),eJ("response");try{let t=await (0,es.sendRequest)(e),s={...R,[eK.id]:t};E(s),z(eb(e_,s))}finally{C(!1)}}}async function e2(e,t){let s=Object.fromEntries(await Promise.all(e.map(async e=>[e.id,await (0,es.sendRequest)(t.get(e.id))])));return E(e=>({...e,...s})),s}async function e6(){let e;if(!S&&!ew&&eW){try{e=new Map(await Promise.all(e_.map(async e=>[e.id,await e0(e)])))}catch(e){F(e instanceof Error?e.message:"Invalid request");return}C(!0),F(null),q(!1);try{let t=await e2(e_,e),s=eb(e_,t);z(s),eJ("response"),r("pass"===s.level?"Every API answered with the same structure":"Checked — see the structure result","pass"===s.level?"success":"info")}finally{C(!1)}}}async function e5(e,t){let s=0;for(;;){let a=await (0,es.fetchRun)(t);if(a){if(s=0,eS(t=>({...t,[e.id]:a})),(0,es.isTerminalStatus)(a.status))return eH.current.delete(t),eV.current||e$(t=>[...t.filter(t=>t.tabId!==e.id),{tabId:e.id,title:e.title,report:a.report}]),eS(({[e.id]:t,...s})=>s),eV.current?null:a.report}else if(++s>=20)throw Error(`Lost contact with the engine while ${e.title} was running`);await new Promise(e=>window.setTimeout(e,500))}}async function e4(e,t){let s=await (0,es.startLoadRun)(t);return eH.current.add(s.id),eS(t=>({...t,[e.id]:s})),s}async function e3(e,t){let s=[];for(let a=0;a<e.length&&!eV.current;a++){let r=e[a];eO(a>0?"between":"running"),k(e=>({...e,activeId:r.id}));let n=await e4(r,t.get(r.id));eO("running");let l=await e5(r,n.id);if(!l)break;s.push({tabId:r.id,title:r.title,report:l})}return s}async function e8(e,t){eO("running");let s=await Promise.allSettled(e.map(e=>e4(e,t.get(e.id)))),a=s.find(e=>"rejected"===e.status);if(a)throw await e9(),a.reason instanceof Error?a.reason:Error("Failed to start load");return(await Promise.all(e.map(async(e,t)=>{let a=s[t].value,r=await e5(e,a.id);return r?{tabId:e.id,title:e.title,report:r}:null}))).filter(e=>!!e)}async function e7(e){let s;if(S||ew||!eW||!f.k6Ready)return;if(eX)return void F(eX);try{s=new Map(await Promise.all(e_.map(async e=>[e.id,await e0(e)])))}catch(e){F(e instanceof Error?e.message:"Invalid request");return}let a=e_.filter(e=>(0,es.needsLoadConfirm)(j.load.preset,s.get(e.id).load,s.get(e.id).request.url));if(a.length>0&&!await o({title:`Start a ${(0,J.profileSummary)(j.load)} load test against ${1===a.length?(0,es.hostOf)(s.get(a[0].id).request.url):`${a.length} hosts`}?`,body:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("ul",{className:"mb-2 list-disc pl-5 font-mono text-xs text-ink",children:a.map(e=>(0,t.jsx)("li",{children:(0,es.hostOf)(s.get(e.id).request.url)},e.id))}),"parallel"===L?"All APIs run at the same time":"APIs run one after another"," with"," ",(0,J.profileSummary)(j.load)," each. Only load-test systems you own or are allowed to test.",(0,t.jsxs)("span",{className:"mt-2 block",children:["Each simulated user sends its next request as soon as the last one returns, so a few dozen users can mean thousands of requests per second. Public sites and APIs behind a CDN or firewall usually answer that with ",(0,t.jsx)("span",{className:"text-ink",children:"429"})," or"," ",(0,t.jsx)("span",{className:"text-ink",children:"403"})," and may block this computer for a while — test a staging server, or use Requests per second mode with a modest rate."]})]}),confirmLabel:"Start compare",tone:"danger"}))return;let n=e||Q;eV.current=!1,eH.current.clear(),F(null),C(!0),ej(!0),eP(!1),eR(!1),e$([]),eS({}),ev(!1),eL(new Date().toISOString()),eU(null),eO("probing"),eJ("results");try{let e=await e2(e_,s),t=e_.filter(t=>!(0,es.isSuccess)(e[t.id]));if(t.length){F(`Every API needs a 2xx check before load. Not 2xx: ${t.map(t=>`${t.title} (${e[t.id]?.status||"error"})`).join(", ")}`),k(e=>({...e,activeId:t[0].id})),eJ("response"),eO("idle");return}let a=eb(e_,e);if(z(a),"fail"===a.level&&!n){q(!0),eJ("response"),eO("idle");return}q(!1),"fail"===a.level&&ev(!0);let l="parallel"===L?await e8(e_,s):await e3(e_,s);if(eU(new Date().toISOString()),l.length>=2&&!eV.current){try{await ec.historyApi.saveCompare({mode:L,ignoreStructure:n,apis:l.map(t=>({id:t.tabId,title:t.title,report:t.report,probe:e[t.tabId]??null}))}),r("Compare finished and saved to History","success"),(0,ed.notifyFinished)({title:"Compare finished",body:`${l.map(e=>e.title).join(" vs ")}
|
|
51
|
-
Saved to History.`,ok:!0})}catch(e){r(`Compare finished, but saving it failed: ${e instanceof Error?e.message:"unknown error"}`,"error")}eT()}else eV.current&&r("Compare stopped","info");eO(eV.current?"idle":"done")}catch(e){F(e instanceof Error?e.message:"Compare failed"),eO("idle")}finally{eH.current.size>0&&await e9(),C(!1),ej(!1),eN(!1),eS({})}}async function e9(){await Promise.all([...eH.current].map(e=>(0,es.stopLoadRun)(e)))}async function te(){eV.current=!0,eN(!0),await e9()}function tt(e){e$(e.apis.map(e=>({tabId:e.id,title:e.title,report:e.report})));let t={};for(let s of e.apis)s.probe&&(t[s.id]=s.probe);E(t),M(e.mode),eg(!!e.meta?.ignoreStructure),ev(!!e.meta?.ignoreStructure),eP(!0),eR(!1),q(!1),z(null),eO("done");let s=e.apis.map(e=>Date.parse(e.report.startedAt)).filter(Number.isFinite),a=e.apis.map(e=>Date.parse(e.report.endedAt)).filter(Number.isFinite);eL(s.length?new Date(Math.min(...s)).toISOString():e.exportedAt),eU(a.length?new Date(Math.max(...a)).toISOString():e.exportedAt),eJ("results")}async function ts(e){try{tt(await ec.historyApi.compare(e.id))}catch(e){r(e instanceof Error?e.message:"Could not open that compare","error")}}(0,ep.useShortcuts)({send:()=>void e1(),run:()=>void e7(!1),focusUrl:()=>w.current?.focus()});let ta=R[eK.id],tr=f.k6Ready?eW?eX??void 0:"Give at least two APIs a URL":f.k6Label,tn=ez.length>0?(0,t.jsx)(eh,{entries:ez,disabled:ew,onPick:e=>void ts(e)}):null;if("results"===eB){let e=eG.length||e_.length;return(0,t.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col",children:[(0,t.jsx)("h1",{className:"sr-only",children:"Compare APIs"}),(0,t.jsx)(D,{items:eG,liveTabs:new Set(Object.keys(eI)),running:ew,source:eF?"saved":"compare",mode:L,slo:g,title:eF?`Comparing ${e} saved runs`:eA?`Saved compare \xb7 ${e} APIs`:ew?`Comparing ${e} APIs…`:`Compare results \xb7 ${e} APIs`,actions:ew?(0,t.jsxs)("button",{type:"button",className:"btn btn-danger",disabled:ek,onClick:()=>void te(),children:[ek?(0,t.jsx)(n.Loader2,{size:16,className:"animate-spin"}):(0,t.jsx)(p.Square,{size:16}),ek?"Stopping…":"Stop"]}):(0,t.jsxs)(t.Fragment,{children:[tn,eA?(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",onClick:()=>{e$([]),E({}),eP(!1),eR(!1),eO("idle"),eJ("response"),s.replace("/compare")},title:"Close these results and set up a new compare",children:[(0,t.jsx)(m.X,{size:16}),"Close"]}):(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",onClick:()=>eJ("response"),title:"Back to the APIs and the shared load",children:[(0,t.jsx)(l,{size:16}),"Edit setup"]})]}),status:eF?null:(0,t.jsx)(U,{mode:L,phase:eD,liveRuns:eQ,startedAt:eq,endedAt:eM}),exportBar:eZ.length>0&&!ew?(0,t.jsx)(A,{apis:eZ,mode:L,probes:R}):null})]})}return(0,t.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col",children:[(0,t.jsx)("h1",{className:"sr-only",children:"Compare APIs"}),(0,t.jsx)(eu,{tabs:j.tabs,activeId:eK.id,probes:R,liveByTab:eI,reports:eC,running:ew,onSelect:e=>k(t=>({...t,activeId:e})),onRename:(e,t)=>eY(e,{...j.tabs.find(t=>t.id===e),title:t}),onClose:function(e){j.tabs.length<=2||ew||(k(t=>{let s=t.tabs.findIndex(t=>t.id===e),a=t.tabs.filter(t=>t.id!==e),r=t.activeId===e?a[Math.max(0,s-1)]?.id??a[0].id:t.activeId;return{...t,tabs:a,activeId:r}}),E(({[e]:t,...s})=>s),e$(t=>t.filter(t=>t.tabId!==e)))},onAdd:function(){if(j.tabs.length>=6)return;let e=Y(`API ${j.tabs.length+1}`);k(t=>({...t,tabs:[...t.tabs,e],activeId:e.id})),window.setTimeout(()=>w.current?.focus(),0)}}),(0,t.jsxs)("header",{className:"border-b border-line bg-panel px-4 py-3 lg:px-6",children:[(0,t.jsx)(V.RequestBar,{ref:w,draft:eK,onChange:e=>eY(eK.id,{...eK,...e}),onImported:e=>r(e,"success"),actions:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("button",{type:"button",className:"btn btn-primary",disabled:S||ew||!(0,G.isReady)(eK),title:`Send this API once (${b}+Enter)`,onClick:()=>void e1(),children:[S&&!ew?(0,t.jsx)(n.Loader2,{size:16,className:"animate-spin"}):(0,t.jsx)(d.Send,{size:16}),"Send"]}),(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",disabled:S||ew||!eW,title:"Send every API once and compare response structure",onClick:()=>void e6(),children:[(0,t.jsx)(c,{size:16}),"Send all"]}),ew?(0,t.jsxs)("button",{type:"button",className:"btn btn-danger",disabled:ek,onClick:()=>void te(),children:[ek?(0,t.jsx)(n.Loader2,{size:16,className:"animate-spin"}):(0,t.jsx)(p.Square,{size:16}),ek?"Stopping…":"Stop"]}):(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",disabled:S||!!tr,title:tr??`Check every API, then run the shared load on each (${b}+Shift+Enter)`,onClick:()=>void e7(!1),children:[(0,t.jsx)(i.Play,{size:16}),"Compare load"]})]})}),(0,t.jsxs)("div",{className:"mt-2 flex flex-wrap items-center gap-x-4 gap-y-2",children:[(0,t.jsx)(er.EnvironmentBar,{drafts:j.tabs,collections:j.tabs.map(e=>e.collection)}),(0,t.jsx)(en.RequestPicker,{onOpen:e=>{eY(eK.id,{...eK,...(0,G.requestFromUnknown)(e.request),id:eK.id,title:e.name,collection:e.collection||void 0}),r(`Loaded “${e.name}” into this tab`,"success")}}),P?(0,t.jsx)("p",{role:"alert",className:"text-xs text-bad",children:P}):null]})]}),(0,t.jsxs)("div",{className:"grid min-h-0 flex-1 overflow-y-auto lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:overflow-hidden 2xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]",children:[(0,t.jsxs)("section",{"aria-label":"Request",className:"min-w-0 space-y-6 px-4 py-4 lg:overflow-y-auto lg:px-6",children:[(0,t.jsx)(H.RequestEditor,{id:"compare-request",draft:eK,collection:eK.collection,onChange:e=>eY(eK.id,{...eK,...e}),checkResults:R[eK.id]?.checks,setupResult:R[eK.id]?.setup}),(0,t.jsxs)("section",{className:"space-y-4 rounded-lg border border-line bg-panel p-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("h2",{className:"flex items-center gap-1.5 text-sm font-medium text-ink",children:["Compare setup",(0,t.jsx)(B.Hint,{text:"These settings apply to every API, so their timelines line up in the overlay."})]}),(0,t.jsxs)("p",{className:"mt-0.5 text-xs text-mute",children:["Applies to all ",j.tabs.length," APIs."]})]}),(0,t.jsx)(J.LoadEditor,{load:j.load,onChange:e=>k(t=>({...t,load:e}))}),(0,t.jsxs)("div",{className:"space-y-2 border-t border-line pt-4",children:[(0,t.jsx)("span",{className:"block text-xs text-mute",children:"Run mode"}),(0,t.jsx)(W.Segmented,{label:"Run mode",value:L,onChange:M,options:[{value:"sequence",label:"One after another"},{value:"parallel",label:"All at once"}]}),(0,t.jsx)("p",{className:"text-xs text-mute",children:"sequence"===L?"Fairest comparison: each API gets this computer's full CPU and network.":"Faster, but the runs share CPU and network, so treat the numbers as directional."}),(0,t.jsxs)("label",{className:"flex items-start gap-2 pt-1 text-sm text-ink",children:[(0,t.jsx)("input",{type:"checkbox",checked:Q,onChange:e=>eg(e.target.checked),className:"mt-1 accent-[var(--accent)]"}),(0,t.jsxs)("span",{children:["Allow different response structures",(0,t.jsx)("span",{className:"block text-xs text-mute",children:"By default every API must return the same JSON shape, so you compare like with like. Each still needs a 2xx."})]})]})]})]})]}),(0,t.jsxs)("section",{"aria-label":"Response",className:"@container flex min-h-[32rem] min-w-0 flex-col border-line bg-panel/40 px-4 py-4 lg:min-h-0 lg:overflow-y-auto lg:border-l lg:px-6",children:[(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-2 border-b border-line pb-2",children:[(0,t.jsxs)("h2",{className:"text-sm font-medium text-ink",children:["Response · ",eK.title||"API",ta?(0,t.jsx)("span",{className:`ml-2 font-mono text-xs ${(0,es.isSuccess)(ta)?"text-ok":"text-bad"}`,children:ta.status||"ERR"}):null]}),(0,t.jsxs)("div",{className:"ml-auto flex items-center gap-2",children:[tn,eG.length>=2?(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary py-1 text-xs",onClick:()=>eJ("results"),children:["View results",(0,t.jsx)(a,{size:14})]}):null]})]}),(0,t.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col space-y-4 pt-4",children:[(0,t.jsx)(ex,{contract:T,blocked:O,skipped:ey,busy:S||ew,onProceed:()=>void e7(!0)}),ta?(0,t.jsx)(K.ResponseViewer,{result:ta}):(0,t.jsxs)(ef,{children:[(0,t.jsxs)("p",{children:["Give each tab a URL, choose the shared load, then press ",(0,t.jsx)("span",{className:"text-ink",children:"Compare load"}),". Each API is checked first; the results open full width, with a tab for each API."]}),(0,t.jsxs)("p",{className:"mt-2",children:[(0,t.jsx)("span",{className:"text-ink",children:"Send"})," checks this API once; ",(0,t.jsx)("span",{className:"text-ink",children:"Send all"})," checks every API and confirms they return the same structure. To compare finished runs, use ",(0,t.jsx)("span",{className:"text-ink",children:"Add to compare"})," on a report, or tick runs on"," ",(0,t.jsx)(u.default,{href:"/history",className:"text-accent hover:underline",children:"History"}),"."]})]})]})]})]})]})}],49186)}]);
|
|
51
|
+
Saved to History.`,ok:!0})}catch(e){r(`Compare finished, but saving it failed: ${e instanceof Error?e.message:"unknown error"}`,"error")}eT()}else eV.current&&r("Compare stopped","info");eO(eV.current?"idle":"done")}catch(e){F(e instanceof Error?e.message:"Compare failed"),eO("idle")}finally{eH.current.size>0&&await e9(),C(!1),ej(!1),eN(!1),eS({})}}async function e9(){await Promise.all([...eH.current].map(e=>(0,es.stopLoadRun)(e)))}async function te(){eV.current=!0,eN(!0),await e9()}function tt(e){e$(e.apis.map(e=>({tabId:e.id,title:e.title,report:e.report})));let t={};for(let s of e.apis)s.probe&&(t[s.id]=s.probe);E(t),M(e.mode),eg(!!e.meta?.ignoreStructure),ev(!!e.meta?.ignoreStructure),eP(!0),eR(!1),q(!1),z(null),eO("done");let s=e.apis.map(e=>Date.parse(e.report.startedAt)).filter(Number.isFinite),a=e.apis.map(e=>Date.parse(e.report.endedAt)).filter(Number.isFinite);eL(s.length?new Date(Math.min(...s)).toISOString():e.exportedAt),eU(a.length?new Date(Math.max(...a)).toISOString():e.exportedAt),eJ("results")}async function ts(e){try{tt(await ec.historyApi.compare(e.id))}catch(e){r(e instanceof Error?e.message:"Could not open that compare","error")}}(0,ep.useShortcuts)({send:()=>void e1(),run:()=>void e7(!1),focusUrl:()=>w.current?.focus()});let ta=R[eK.id],tr=f.k6Ready?eW?eX??void 0:"Give at least two APIs a URL":f.k6Label,tn=ez.length>0?(0,t.jsx)(eh,{entries:ez,disabled:ew,onPick:e=>void ts(e)}):null;if("results"===eB){let e=eG.length||e_.length;return(0,t.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col",children:[(0,t.jsx)("h1",{className:"sr-only",children:"Compare APIs"}),(0,t.jsx)(D,{items:eG,liveTabs:new Set(Object.keys(eI)),running:ew,source:eF?"saved":"compare",mode:L,slo:g,title:eF?`Comparing ${e} saved runs`:eA?`Saved compare \xb7 ${e} APIs`:ew?`Comparing ${e} APIs…`:`Compare results \xb7 ${e} APIs`,actions:ew?(0,t.jsxs)("button",{type:"button",className:"btn btn-danger",disabled:ek,onClick:()=>void te(),children:[ek?(0,t.jsx)(n.Loader2,{size:16,className:"animate-spin"}):(0,t.jsx)(p.Square,{size:16}),ek?"Stopping…":"Stop"]}):(0,t.jsxs)(t.Fragment,{children:[tn,eA?(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",onClick:()=>{e$([]),E({}),eP(!1),eR(!1),eO("idle"),eJ("response"),s.replace("/compare")},title:"Close these results and set up a new compare",children:[(0,t.jsx)(m.X,{size:16}),"Close"]}):(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",onClick:()=>eJ("response"),title:"Back to the APIs and the shared load",children:[(0,t.jsx)(l,{size:16}),"Edit setup"]})]}),status:eF?null:(0,t.jsx)(U,{mode:L,phase:eD,liveRuns:eQ,startedAt:eq,endedAt:eM}),exportBar:eZ.length>0&&!ew?(0,t.jsx)(A,{apis:eZ,mode:L,probes:R}):null})]})}return(0,t.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col",children:[(0,t.jsx)("h1",{className:"sr-only",children:"Compare APIs"}),(0,t.jsx)(eu,{tabs:j.tabs,activeId:eK.id,probes:R,liveByTab:eI,reports:eC,running:ew,onSelect:e=>k(t=>({...t,activeId:e})),onRename:(e,t)=>eY(e,{...j.tabs.find(t=>t.id===e),title:t}),onClose:function(e){j.tabs.length<=2||ew||(k(t=>{let s=t.tabs.findIndex(t=>t.id===e),a=t.tabs.filter(t=>t.id!==e),r=t.activeId===e?a[Math.max(0,s-1)]?.id??a[0].id:t.activeId;return{...t,tabs:a,activeId:r}}),E(({[e]:t,...s})=>s),e$(t=>t.filter(t=>t.tabId!==e)))},onAdd:function(){if(j.tabs.length>=6)return;let e=Y(`API ${j.tabs.length+1}`);k(t=>({...t,tabs:[...t.tabs,e],activeId:e.id})),window.setTimeout(()=>w.current?.focus(),0)}}),(0,t.jsxs)("header",{className:"border-b border-line bg-panel px-4 py-3 lg:px-6",children:[(0,t.jsx)(V.RequestBar,{ref:w,draft:eK,onChange:e=>eY(eK.id,{...eK,...e}),onImported:e=>r(e,"success"),actions:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("button",{type:"button",className:"btn btn-primary",disabled:S||ew||!(0,G.isReady)(eK),title:`Send this API once (${b}+Enter)`,onClick:()=>void e1(),children:[S&&!ew?(0,t.jsx)(n.Loader2,{size:16,className:"animate-spin"}):(0,t.jsx)(d.Send,{size:16}),"Send"]}),(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",disabled:S||ew||!eW,title:"Send every API once and compare response structure",onClick:()=>void e6(),children:[(0,t.jsx)(c,{size:16}),"Send all"]}),ew?(0,t.jsxs)("button",{type:"button",className:"btn btn-danger",disabled:ek,onClick:()=>void te(),children:[ek?(0,t.jsx)(n.Loader2,{size:16,className:"animate-spin"}):(0,t.jsx)(p.Square,{size:16}),ek?"Stopping…":"Stop"]}):(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary",disabled:S||!!tr,title:tr??`Check every API, then run the shared load on each (${b}+Shift+Enter)`,onClick:()=>void e7(!1),children:[(0,t.jsx)(i.Play,{size:16}),"Compare load"]})]})}),(0,t.jsxs)("div",{className:"mt-2 flex flex-wrap items-center gap-x-4 gap-y-2",children:[(0,t.jsx)(er.EnvironmentBar,{drafts:j.tabs,collections:j.tabs.map(e=>e.collection)}),(0,t.jsx)(en.RequestPicker,{onOpen:e=>{eY(eK.id,{...eK,...(0,G.requestFromUnknown)(e.request),id:eK.id,title:e.name,collection:e.collection||void 0}),r(`Loaded “${e.name}” into this tab`,"success")}}),P?(0,t.jsx)("p",{role:"alert",className:"text-xs text-bad",children:P}):null]})]}),(0,t.jsxs)("div",{className:"grid min-h-0 flex-1 overflow-y-auto lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:overflow-hidden 2xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]",children:[(0,t.jsxs)("section",{"aria-label":"Request",className:"min-w-0 space-y-6 px-4 py-4 lg:overflow-y-auto lg:px-6",children:[(0,t.jsx)(H.RequestEditor,{id:"compare-request",draft:eK,collection:eK.collection,onChange:e=>eY(eK.id,{...eK,...e}),checkResults:R[eK.id]?.checks,lastBody:R[eK.id]?.body,setupResult:R[eK.id]?.setup}),(0,t.jsxs)("section",{className:"space-y-4 rounded-lg border border-line bg-panel p-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("h2",{className:"flex items-center gap-1.5 text-sm font-medium text-ink",children:["Compare setup",(0,t.jsx)(B.Hint,{text:"These settings apply to every API, so their timelines line up in the overlay."})]}),(0,t.jsxs)("p",{className:"mt-0.5 text-xs text-mute",children:["Applies to all ",j.tabs.length," APIs."]})]}),(0,t.jsx)(J.LoadEditor,{load:j.load,onChange:e=>k(t=>({...t,load:e}))}),(0,t.jsxs)("div",{className:"space-y-2 border-t border-line pt-4",children:[(0,t.jsx)("span",{className:"block text-xs text-mute",children:"Run mode"}),(0,t.jsx)(W.Segmented,{label:"Run mode",value:L,onChange:M,options:[{value:"sequence",label:"One after another"},{value:"parallel",label:"All at once"}]}),(0,t.jsx)("p",{className:"text-xs text-mute",children:"sequence"===L?"Fairest comparison: each API gets this computer's full CPU and network.":"Faster, but the runs share CPU and network, so treat the numbers as directional."}),(0,t.jsxs)("label",{className:"flex items-start gap-2 pt-1 text-sm text-ink",children:[(0,t.jsx)("input",{type:"checkbox",checked:Q,onChange:e=>eg(e.target.checked),className:"mt-1 accent-[var(--accent)]"}),(0,t.jsxs)("span",{children:["Allow different response structures",(0,t.jsx)("span",{className:"block text-xs text-mute",children:"By default every API must return the same JSON shape, so you compare like with like. Each still needs a 2xx."})]})]})]})]})]}),(0,t.jsxs)("section",{"aria-label":"Response",className:"@container flex min-h-[32rem] min-w-0 flex-col border-line bg-panel/40 px-4 py-4 lg:min-h-0 lg:overflow-y-auto lg:border-l lg:px-6",children:[(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-2 border-b border-line pb-2",children:[(0,t.jsxs)("h2",{className:"text-sm font-medium text-ink",children:["Response · ",eK.title||"API",ta?(0,t.jsx)("span",{className:`ml-2 font-mono text-xs ${(0,es.isSuccess)(ta)?"text-ok":"text-bad"}`,children:ta.status||"ERR"}):null]}),(0,t.jsxs)("div",{className:"ml-auto flex items-center gap-2",children:[tn,eG.length>=2?(0,t.jsxs)("button",{type:"button",className:"btn btn-secondary py-1 text-xs",onClick:()=>eJ("results"),children:["View results",(0,t.jsx)(a,{size:14})]}):null]})]}),(0,t.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col space-y-4 pt-4",children:[(0,t.jsx)(ex,{contract:T,blocked:O,skipped:ey,busy:S||ew,onProceed:()=>void e7(!0)}),ta?(0,t.jsx)(K.ResponseViewer,{result:ta}):(0,t.jsxs)(ef,{children:[(0,t.jsxs)("p",{children:["Give each tab a URL, choose the shared load, then press ",(0,t.jsx)("span",{className:"text-ink",children:"Compare load"}),". Each API is checked first; the results open full width, with a tab for each API."]}),(0,t.jsxs)("p",{className:"mt-2",children:[(0,t.jsx)("span",{className:"text-ink",children:"Send"})," checks this API once; ",(0,t.jsx)("span",{className:"text-ink",children:"Send all"})," checks every API and confirms they return the same structure. To compare finished runs, use ",(0,t.jsx)("span",{className:"text-ink",children:"Add to compare"})," on a report, or tick runs on"," ",(0,t.jsx)(u.default,{href:"/history",className:"text-accent hover:underline",children:"History"}),"."]})]})]})]})]})]})}],49186)}]);
|