fixtureqa 0.4.24__tar.gz → 0.4.25__tar.gz
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.
- {fixtureqa-0.4.24/fixtureqa.egg-info → fixtureqa-0.4.25}/PKG-INFO +1 -1
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_payload.py +7 -2
- fixtureqa-0.4.24/fixture/static/assets/index-B3ccKxdv.js → fixtureqa-0.4.25/fixture/static/assets/index-76KAxdQ5.js +1 -1
- fixtureqa-0.4.24/fixture/static/assets/index-meqdijDf.css → fixtureqa-0.4.25/fixture/static/assets/index-C9bcm_lN.css +1 -1
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/static/index.html +2 -2
- {fixtureqa-0.4.24 → fixtureqa-0.4.25/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixtureqa.egg-info/SOURCES.txt +2 -2
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/pyproject.toml +1 -1
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_perf_engine.py +29 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/LICENSE +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/README.md +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/__init__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/__main__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/app.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/deps.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/auth.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/events.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/latency_csv_writer.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/models.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_charts.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_engine.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_models.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_stats.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/session.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/server.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/setup.cfg +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_auth.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_config_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_health.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_inbound.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_message_store.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_session_auto_response.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_sessions.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_templates.py +0 -0
- {fixtureqa-0.4.24 → fixtureqa-0.4.25}/tests/test_ws.py +0 -0
|
@@ -503,8 +503,13 @@ class PayloadFactory:
|
|
|
503
503
|
order_qty = ctx["qty"]
|
|
504
504
|
msg.set_field(38, str(order_qty))
|
|
505
505
|
self._apply_expiry(msg, fields)
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
# Mirror the values that actually went on the wire (template-rendered when
|
|
507
|
+
# present, else the ctx defaults we just set) so amend/cancel 35=G/35=F
|
|
508
|
+
# echo the order's real Symbol(55)/Side(54), not the ORDER PAYLOAD section.
|
|
509
|
+
return msg, {"corr_id": corr, "clordid": clordid,
|
|
510
|
+
"symbol": rendered.get(55, ctx["symbol"]),
|
|
511
|
+
"side": rendered.get(54, ctx["side"]),
|
|
512
|
+
"qty": ctx["qty"], "price": ctx["price"],
|
|
508
513
|
"order_qty": order_qty, "msg_type": mtype,
|
|
509
514
|
"mirror_tags": self._mirror_tags(msg)}
|
|
510
515
|
|
|
@@ -99,4 +99,4 @@ Please change the parent <Route path="${b}"> to <Route path="${b==="/"?"*":`${b}
|
|
|
99
99
|
]
|
|
100
100
|
}
|
|
101
101
|
]
|
|
102
|
-
}`;function fW({onClose:e}){const[t,r]=h.useState(""),[n,a]=h.useState(""),[i,s]=h.useState(!1);h.useEffect(()=>{G.specOverlay.get().then(c=>r(JSON.stringify(c,null,2))).catch(console.error)},[]);async function o(){a("");let c;try{c=JSON.parse(t)}catch{a("Invalid JSON");return}s(!0);try{await G.specOverlay.set(c),e()}catch(u){a(u instanceof Error?u.message:"Save failed")}finally{s(!1)}}return l.jsx("div",{className:"modal-overlay",onClick:e,children:l.jsxs("div",{className:"modal",style:{width:660,maxHeight:"85vh",display:"flex",flexDirection:"column"},onClick:c=>c.stopPropagation(),children:[l.jsxs("div",{className:"modal-header",children:[l.jsx("span",{children:"Spec Overlay — Custom Message Types"}),l.jsx("button",{className:"btn-icon",onClick:e,children:"✕"})]}),l.jsx("div",{style:{padding:"10px 16px 4px",fontSize:12,color:"var(--text2)"},children:"Define custom / proprietary message types (e.g. 35=U1). Fields reference standard tag numbers; use Custom Tags for non-standard tag names. Field names and types are resolved from the spec at runtime."}),l.jsx("div",{style:{flex:1,display:"flex",flexDirection:"column",padding:"8px 16px",minHeight:0},children:l.jsx("textarea",{style:{flex:1,minHeight:300,fontFamily:"var(--font)",fontSize:12,background:"var(--bg3)",color:"var(--text)",border:"1px solid var(--border)",borderRadius:4,padding:"8px 10px",resize:"none"},value:t,onChange:c=>r(c.target.value),spellCheck:!1,placeholder:dW})}),l.jsxs("div",{style:{padding:"0 16px 8px",fontSize:11,color:"var(--text2)"},children:["Schema: ",l.jsx("code",{children:'{ "messages": [{ "msg_type", "name", "category", "fields": [{ "tag", "required" }] }] }'})]}),n&&l.jsx("div",{className:"tpl-error",style:{margin:"0 16px 8px"},children:n}),l.jsxs("div",{className:"modal-footer",children:[l.jsx("button",{className:"btn btn-ghost",onClick:e,children:"Cancel"}),l.jsx("button",{className:"btn btn-primary",onClick:o,disabled:i,children:i?"Saving…":"Save overlay"})]})]})})}const Zw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgCgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGNpcmNsZSBjeD0iMjA3IiBjeT0iNzgiIHI9IjEwIiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjY1IiB4Mj0iMjQ4IiB5Mj0iNjUiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDAsIDE4MCwgMjAwKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMCwgMTgwLCAyMDApO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNztmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI5MSIgeDI9IjI0NCIgeTI9IjkxIiBzdHJva2U9IiMwMGI0YzgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjQ1IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigwLCAxODAsIDIwMCk7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC40NTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgoKICA8dGV4dCB4PSIyNzIiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtbW9ubykiIGZvbnQtc2l6ZT0iNjYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6NTBweDtmb250LXdlaWdodDozMDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byI+dHVyZTwvdGV4dD4KPC9zdmc+",qw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSIxMCIgZmlsbD0iIzFhM2E1YyIgc3R5bGU9ImZpbGw6cmdiKDI2LCA1OCwgOTIpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI2NSIgeDI9IjI0OCIgeTI9IjY1IiBzdHJva2U9IiMxYTNhNWMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigyNiwgNTgsIDkyKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMjYsIDU4LCA5Mik7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC43O2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjkxIiB4Mj0iMjQ0IiB5Mj0iOTEiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNDUiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDI2LCA1OCwgOTIpO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNDU7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJnF1b3Q7U2Vnb2UgVUkmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7Zm9udC13ZWlnaHQ6NDAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iLz4KCiAgPHRleHQgeD0iMjcyIiB5PSIxMDUiIGZvbnQtZmFtaWx5PSJ2YXIoLS1mb250LW1vbm8pIiBmb250LXNpemU9IjY2IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMWEzYTVjIiBsZXR0ZXItc3BhY2luZz0iLTEiIHN0eWxlPSJmaWxsOnJnYigyNiwgNTgsIDkyKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjUwcHg7Zm9udC13ZWlnaHQ6MzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPnR1cmU8L3RleHQ+Cjwvc3ZnPg==";function tl(){const e=X(r=>r.theme),t=X(r=>r.branding);return e==="dark"?(t==null?void 0:t.logo_dark)||Zw:(t==null?void 0:t.logo_light)||qw}function hW(){const e=X(g=>g.setSessions),t=X(g=>g.theme),r=X(g=>g.toggleTheme),n=X(g=>g.user),a=X(g=>g.logout),i=X(g=>g.openTabs),s=X(g=>g.activeTabId),o=X(g=>g.branding),c=tl(),u=o!=null&&o.prefix?`${o.prefix} FIXture`:"FIXture",[f,d]=h.useState(!1),[m,p]=h.useState(!1),[v,y]=h.useState(!1);return Lv(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{G.sessions.list().then(e).catch(console.error)},[]),h.useEffect(()=>{if(!n){j_();return}G.customTags.list().then($v).catch(console.error)},[n]),l.jsxs("div",{className:"app",children:[l.jsxs("header",{className:"app-header",children:[l.jsx("img",{src:c,alt:u,className:"app-logo"}),l.jsxs("div",{className:"header-right",children:[n&&l.jsx("span",{className:"header-user",children:n.username}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>d(!0),children:"Scenarios"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p(!0),children:"Custom Tags"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>y(!0),children:"Spec Overlay"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(tn,{to:"/perf",className:"btn btn-sm btn-ghost",title:"Performance testing",children:"Perf"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(tn,{to:"/admin/users",className:"btn-icon",title:"Admin",children:"⚙"}),l.jsx("button",{className:"btn-icon theme-toggle",onClick:r,title:"Toggle theme",children:t==="dark"?"☀":"🌙"}),l.jsx("button",{className:"btn-icon",onClick:a,title:"Sign out",children:"⎋"})]})]}),l.jsxs("div",{className:"app-body",children:[l.jsx(R_,{}),l.jsx("div",{className:"tab-area",children:i.length===0?l.jsx("div",{className:"tab-area-empty",children:"Select a session to open it."}):l.jsxs(l.Fragment,{children:[l.jsx(Yz,{}),l.jsx("div",{className:"tab-panels",children:i.map(g=>l.jsx("div",{className:`tab-panel${g===s?" active":""}`,children:l.jsx(qz,{sessionId:g})},g))})]})})]}),f&&l.jsx(iW,{onClose:()=>d(!1)}),m&&l.jsx(uW,{onClose:()=>p(!1)}),v&&l.jsx(fW,{onClose:()=>y(!1)})]})}function mW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),[c,u]=h.useState(!1),{login:f}=X(),d=tl(),m=fn();h.useEffect(()=>{fetch("/api/auth/register/status").then(v=>v.json()).then(v=>u(v.enabled)).catch(()=>{})},[]);async function p(v){v.preventDefault(),i(""),o(!0);try{const y=await fetch("/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:r})});if(!y.ok){const b=await y.json().catch(()=>({}));throw new Error(b.detail??"Login failed")}const g=await y.json();f(g.access_token,g.user),m("/",{replace:!0})}catch(y){i(y.message)}finally{o(!1)}}return l.jsx("div",{className:"auth-page",children:l.jsxs("div",{className:"auth-card",children:[l.jsx("img",{src:d,alt:"FIXture",className:"auth-logo"}),l.jsx("h2",{children:"Sign in"}),l.jsxs("form",{onSubmit:p,className:"auth-form",children:[l.jsxs("label",{children:["Username",l.jsx("input",{type:"text",value:e,onChange:v=>t(v.target.value),autoComplete:"username",required:!0})]}),l.jsxs("label",{children:["Password",l.jsx("input",{type:"password",value:r,onChange:v=>n(v.target.value),autoComplete:"current-password",required:!0})]}),a&&l.jsx("p",{className:"auth-error",children:a}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Signing in…":"Sign in"})]}),c&&l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["No account? ",l.jsx(tn,{to:"/register",children:"Register"})]})]})})}function pW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),c=tl(),u=fn();async function f(d){d.preventDefault(),i(""),o(!0);try{const m=await fetch("/api/setup/create-admin",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:r})});if(!m.ok){const p=await m.json().catch(()=>({}));throw new Error(p.detail??"Setup failed")}u("/login",{replace:!0})}catch(m){i(m.message)}finally{o(!1)}}return l.jsx("div",{className:"auth-page",children:l.jsxs("div",{className:"auth-card",children:[l.jsx("img",{src:c,alt:"FIXture",className:"auth-logo"}),l.jsx("h2",{children:"Create admin account"}),l.jsx("p",{className:"auth-hint",children:"First-time setup — create the platform admin."}),l.jsxs("form",{onSubmit:f,className:"auth-form",children:[l.jsxs("label",{children:["Username",l.jsx("input",{type:"text",value:e,onChange:d=>t(d.target.value),autoComplete:"username",required:!0})]}),l.jsxs("label",{children:["Password",l.jsx("input",{type:"password",value:r,onChange:d=>n(d.target.value),autoComplete:"new-password",minLength:8,required:!0})]}),a&&l.jsx("p",{className:"auth-error",children:a}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Creating…":"Create admin"})]})]})})}function vW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),[c,u]=h.useState(null),{login:f,theme:d}=X(),m=fn();h.useEffect(()=>{fetch("/api/auth/register/status").then(y=>y.json()).then(u).catch(()=>u({enabled:!1,max_users:0,current_users:0}))},[]);async function p(y){y.preventDefault(),i(""),o(!0);try{const g=await fetch("/api/auth/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:r})});if(!g.ok){const w=await g.json().catch(()=>({}));throw new Error(w.detail??"Registration failed")}const b=await g.json();f(b.access_token,b.user),m("/",{replace:!0})}catch(g){i(g.message)}finally{o(!1)}}const v=c&&c.current_users>=c.max_users;return l.jsx("div",{className:"auth-page",children:l.jsxs("div",{className:"auth-card",children:[l.jsx("img",{src:d==="dark"?Zw:qw,alt:"FIXture",className:"auth-logo"}),l.jsx("h2",{children:"Create account"}),c===null&&l.jsx("p",{className:"auth-hint",children:"Checking registration status…"}),c&&!c.enabled&&l.jsx("p",{className:"auth-error",children:"Registration is currently closed. Contact an administrator."}),c&&c.enabled&&v&&l.jsx("p",{className:"auth-error",children:"Registration limit reached. Contact an administrator."}),c&&c.enabled&&!v&&l.jsxs("form",{onSubmit:p,className:"auth-form",children:[l.jsxs("label",{children:["Username",l.jsx("input",{type:"text",value:e,onChange:y=>t(y.target.value),autoComplete:"username",required:!0})]}),l.jsxs("label",{children:["Password",l.jsx("input",{type:"password",value:r,onChange:y=>n(y.target.value),autoComplete:"new-password",minLength:8,required:!0})]}),a&&l.jsx("p",{className:"auth-error",children:a}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Creating account…":"Create account"})]}),l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["Already have an account? ",l.jsx(tn,{to:"/login",children:"Sign in"})]})]})})}const gW=["accent","bg","bg2","bg3","border","text","text2"];function Kw(e){let t=document.getElementById("branding-style");t||(t=document.createElement("style"),t.id="branding-style",document.head.appendChild(t));const r=gW.filter(n=>e[n]).map(n=>`--${n}: ${e[n]};`).join(" ");if(r?t.textContent=`:root { ${r} } [data-theme="light"] { ${r} }`:t.textContent="",e.favicon){let n=document.querySelector('link[rel="icon"]');n||(n=document.createElement("link"),n.rel="icon",document.head.appendChild(n)),n.href=e.favicon}document.title=e.prefix?`${e.prefix} FIXture`:"FIXture"}function Dl(e){return new Promise((t,r)=>{const n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}const Yw=["user","user_admin","platform_admin"];function yW(e){return new Date(e*1e3).toLocaleString()}function bW({onClose:e,onCreated:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState("user"),[c,u]=h.useState(""),[f,d]=h.useState(!1);async function m(p){p.preventDefault(),u(""),d(!0);try{const v=await G.admin.users.create({username:r,password:a,role:s});t(v),e()}catch(v){u(v.message)}finally{d(!1)}}return l.jsx("div",{className:"modal-overlay",onClick:e,children:l.jsxs("div",{className:"modal",onClick:p=>p.stopPropagation(),children:[l.jsxs("div",{className:"modal-header",children:[l.jsx("span",{children:"Create User"}),l.jsx("button",{className:"btn-icon",onClick:e,children:"✕"})]}),l.jsxs("form",{onSubmit:m,children:[l.jsxs("div",{className:"form-grid",children:[l.jsx("label",{children:"Username"}),l.jsx("input",{value:r,onChange:p=>n(p.target.value),required:!0}),l.jsx("label",{children:"Password"}),l.jsx("input",{type:"password",value:a,onChange:p=>i(p.target.value),minLength:8,required:!0}),l.jsx("label",{children:"Role"}),l.jsx("select",{value:s,onChange:p=>o(p.target.value),children:Yw.map(p=>l.jsx("option",{value:p,children:p},p))})]}),c&&l.jsx("div",{className:"error-msg",children:c}),l.jsxs("div",{className:"modal-footer",children:[l.jsx("button",{type:"button",className:"btn btn-secondary",onClick:e,children:"Cancel"}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:f,children:f?"Creating…":"Create"})]})]})]})})}function xW({user:e,onClose:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),[c,u]=h.useState(!1);async function f(d){d.preventDefault(),i(""),o(!0);try{await G.admin.users.update(e.uid,{password:r}),u(!0)}catch(m){i(m.message)}finally{o(!1)}}return l.jsx("div",{className:"modal-overlay",onClick:t,children:l.jsxs("div",{className:"modal",onClick:d=>d.stopPropagation(),children:[l.jsxs("div",{className:"modal-header",children:[l.jsxs("span",{children:["Reset Password — ",e.username]}),l.jsx("button",{className:"btn-icon",onClick:t,children:"✕"})]}),c?l.jsxs("div",{style:{padding:16},children:[l.jsx("p",{style:{color:"var(--green)"},children:"Password updated successfully."}),l.jsx("div",{className:"modal-footer",children:l.jsx("button",{className:"btn btn-primary",onClick:t,children:"Close"})})]}):l.jsxs("form",{onSubmit:f,children:[l.jsxs("div",{className:"form-grid",children:[l.jsx("label",{children:"New Password"}),l.jsx("input",{type:"password",value:r,onChange:d=>n(d.target.value),minLength:8,required:!0})]}),a&&l.jsx("div",{className:"error-msg",children:a}),l.jsxs("div",{className:"modal-footer",children:[l.jsx("button",{type:"button",className:"btn btn-secondary",onClick:t,children:"Cancel"}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Saving…":"Save"})]})]})]})})}function wW(){const e=X(I=>I.user),t=fn(),[r,n]=h.useState([]),[a,i]=h.useState(null),[s,o]=h.useState(null),[c,u]=h.useState(null),[f,d]=h.useState(!1),[m,p]=h.useState(null),[v,y]=h.useState(!1),{setBranding:g}=X(),[b,w]=h.useState(!1),[x,j]=h.useState(null),[_,S]=h.useState("");async function O(){try{const[I,R,z,U]=await Promise.all([G.admin.users.list(),G.admin.settings.get(),G.admin.housekeeping.get(),G.admin.branding.get()]);n(I),i(R),o(z),p(U)}catch(I){S(I.message)}}h.useEffect(()=>{O()},[]);async function P(I){try{const R=await G.admin.users.update(I.uid,{is_active:!I.is_active});n(z=>z.map(U=>U.uid===I.uid?R:U))}catch(R){S(R.message)}}async function E(I,R){try{const z=await G.admin.users.update(I.uid,{role:R});n(U=>U.map(J=>J.uid===I.uid?z:J))}catch(z){S(z.message)}}async function k(I){if(confirm(`Delete user "${I.username}"? This cannot be undone.`))try{await G.admin.users.delete(I.uid),n(R=>R.filter(z=>z.uid!==I.uid))}catch(R){S(R.message)}}async function T(){if(a)try{const I=await G.admin.settings.update({registration_enabled:!a.enabled});i(I)}catch(I){S(I.message)}}async function C(I){const R=parseInt(I,10);if(!(isNaN(R)||R<1))try{const z=await G.admin.settings.update({max_users:R});i(z)}catch(z){S(z.message)}}async function W(){if(s)try{const I=await G.admin.housekeeping.update({enabled:!s.enabled});o(I)}catch(I){S(I.message)}}async function L(I,R){const z=parseInt(R,10);if(!(isNaN(z)||z<0))try{const U=await G.admin.housekeeping.update({[I]:z});o(U)}catch(U){S(U.message)}}async function H(){d(!0),u(null);try{const I=await G.admin.housekeeping.runNow();u(`Done — deleted ${I.msgs_deleted} messages, ${I.logs_deleted} log files`)}catch(I){S(I.message)}finally{d(!1)}}return l.jsxs("div",{className:"admin-page",children:[l.jsxs("div",{className:"admin-header",children:[l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[l.jsx("button",{className:"btn-icon",onClick:()=>t("/"),title:"Back",children:"←"}),l.jsx("h1",{children:"User Management"})]}),l.jsx("button",{className:"btn btn-primary btn-sm",onClick:()=>w(!0),children:"+ Create User"})]}),_&&l.jsx("div",{className:"error-msg",style:{margin:"0 0 12px"},children:_}),a&&l.jsxs("div",{className:"admin-settings-bar",children:[l.jsx("span",{className:"admin-settings-label",children:"Self-registration:"}),l.jsx("button",{className:`btn btn-sm ${a.enabled?"btn-stop":"btn-start"}`,onClick:T,children:a.enabled?"Enabled — click to disable":"Disabled — click to enable"}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Max users:"}),l.jsx("input",{type:"number",className:"admin-max-users-input",defaultValue:a.max_users,min:1,onBlur:I=>C(I.target.value)}),l.jsxs("span",{className:"admin-settings-label",style:{color:"var(--text2)"},children:["(",a.current_users," / ",a.max_users," registered)"]})]}),s&&l.jsxs("div",{className:"admin-settings-bar",children:[l.jsx("span",{className:"admin-settings-label",children:"Housekeeping:"}),l.jsx("button",{className:`btn btn-sm ${s.enabled?"btn-stop":"btn-start"}`,onClick:W,children:s.enabled?"Auto — click to disable":"Manual only — click to enable"}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Keep messages (days):"}),l.jsx("input",{type:"number",className:"admin-max-users-input",defaultValue:s.msg_retention_days,min:0,title:"0 = keep forever",onBlur:I=>L("msg_retention_days",I.target.value)}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Keep logs (days):"}),l.jsx("input",{type:"number",className:"admin-max-users-input",defaultValue:s.log_retention_days,min:0,title:"0 = keep forever",onBlur:I=>L("log_retention_days",I.target.value)}),l.jsx("button",{className:"btn btn-sm btn-ghost",style:{marginLeft:16},onClick:H,disabled:f,children:f?"Running…":"Run Now"}),c&&l.jsx("span",{style:{marginLeft:8,color:"var(--green)",fontSize:12},children:c})]}),m&&l.jsxs("div",{className:"admin-settings-bar",style:{flexDirection:"column",alignItems:"flex-start",gap:10},children:[l.jsx("span",{className:"admin-settings-label",style:{fontWeight:700},children:"Branding"}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Brand prefix:"}),l.jsx("input",{className:"admin-max-users-input",style:{width:160},value:m.prefix,placeholder:"e.g. Acme (shown as Acme FIXture)",onChange:I=>p({...m,prefix:I.target.value})}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Accent colour:"}),l.jsx("input",{type:"color",value:m.accent||"#388bfd",onChange:I=>p({...m,accent:I.target.value}),title:"Primary accent colour"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,accent:""}),title:"Reset to default blue",children:"Reset"})]}),l.jsx("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[["bg","Page background","#0d1117"],["bg2","Panel background","#161b22"],["bg3","Input background","#21262d"],["border","Border colour","#30363d"],["text","Primary text","#e6edf3"],["text2","Secondary text","#8b949e"]].map(([I,R,z])=>l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[l.jsxs("span",{className:"admin-settings-label",children:[R,":"]}),l.jsx("input",{type:"color",value:m[I]||z,onChange:U=>p({...m,[I]:U.target.value}),title:R}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,[I]:""}),title:"Reset to default",children:"↺"})]},I))}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Logo (dark theme):"}),m.logo_dark&&l.jsx("img",{src:m.logo_dark,style:{height:28,background:"#161b22",borderRadius:4,padding:"2px 6px"},alt:"dark logo preview"}),l.jsx("input",{type:"file",accept:"image/*",onChange:async I=>{var z;const R=(z=I.target.files)==null?void 0:z[0];R&&p({...m,logo_dark:await Dl(R)})}}),m.logo_dark&&l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,logo_dark:""}),children:"Clear"})]}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Logo (light theme):"}),m.logo_light&&l.jsx("img",{src:m.logo_light,style:{height:28,background:"#f0f2f5",borderRadius:4,padding:"2px 6px"},alt:"light logo preview"}),l.jsx("input",{type:"file",accept:"image/*",onChange:async I=>{var z;const R=(z=I.target.files)==null?void 0:z[0];R&&p({...m,logo_light:await Dl(R)})}}),m.logo_light&&l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,logo_light:""}),children:"Clear"})]}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Favicon:"}),m.favicon&&l.jsx("img",{src:m.favicon,style:{height:20,width:20},alt:"favicon preview"}),l.jsx("input",{type:"file",accept:"image/png,image/svg+xml,image/x-icon",onChange:async I=>{var z;const R=(z=I.target.files)==null?void 0:z[0];R&&p({...m,favicon:await Dl(R)})}}),m.favicon&&l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,favicon:""}),children:"Clear"})]}),l.jsx("button",{className:"btn btn-sm btn-primary",disabled:v,onClick:async()=>{y(!0);try{const I=await G.admin.branding.update(m);p(I),g(I),Kw(I)}catch(I){S(I.message)}finally{y(!1)}},children:v?"Saving…":"Save Branding"})]}),l.jsxs("table",{className:"admin-table",children:[l.jsx("thead",{children:l.jsxs("tr",{children:[l.jsx("th",{children:"Username"}),l.jsx("th",{children:"Role"}),l.jsx("th",{children:"Status"}),l.jsx("th",{children:"Created"}),l.jsx("th",{children:"Actions"})]})}),l.jsx("tbody",{children:r.map(I=>l.jsxs("tr",{className:I.is_active?"":"admin-row-inactive",children:[l.jsxs("td",{children:[I.username,I.uid===(e==null?void 0:e.uid)&&l.jsx("span",{className:"admin-you-badge",children:" (you)"})]}),l.jsx("td",{children:l.jsx("select",{value:I.role,onChange:R=>E(I,R.target.value),className:"admin-role-select",disabled:I.uid===(e==null?void 0:e.uid),children:Yw.map(R=>l.jsx("option",{value:R,children:R},R))})}),l.jsx("td",{children:l.jsx("span",{style:{color:I.is_active?"var(--green)":"var(--text2)"},children:I.is_active?"Active":"Inactive"})}),l.jsx("td",{style:{color:"var(--text2)",fontSize:11},children:yW(I.created_at)}),l.jsx("td",{children:l.jsxs("div",{className:"admin-row-actions",children:[l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>P(I),disabled:I.uid===(e==null?void 0:e.uid),title:I.is_active?"Deactivate":"Activate",children:I.is_active?"Deactivate":"Activate"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>j(I),title:"Reset password",children:"Reset PW"}),l.jsx("button",{className:"btn btn-sm btn-danger",onClick:()=>k(I),disabled:I.uid===(e==null?void 0:e.uid),title:"Delete user",children:"✕"})]})})]},I.uid))})]}),b&&l.jsx(bW,{onClose:()=>w(!1),onCreated:I=>n(R=>[...R,I])}),x&&l.jsx(xW,{user:x,onClose:()=>j(null)})]})}const Lc={enabled:!1,every_n_fills:1,amend_ratio:1,max_per_order:1,price_offset:.01,pending_replace:!1,amend_wait_ms:2e3},Fc={enabled:!1,after_fills:0,cancel_ratio:1,venue_response:"reject",cxl_rej_reason:0},sv={name:"smoke",mode:"loopback",client_session_id:null,venue_session_id:null,correlation_tag:376,exec_id_tag:25116,rate:{orders_per_window:50,order_window_ms:100,dispatch:"smooth",fill_dispatch:"smooth",fills_per_window:50,fill_window_ms:100,fill_ratio:1,allow_burst:!1,max_burst_multiplier:2},test:{duration:30,max_orders:0,scenario_timeout_ms:3e4,max_pending:1e5,on_saturation:"pause",output:"results",record_latency:!0},amend:Lc,cancel:Fc,payload:{variables:{},order:{symbols:["AAPL","MSFT","GOOGL"],side:"alternate",ord_type:"limit",quantity_min:100,quantity_max:1e3,price_min:99,price_max:101,time_in_force:"0"},fill:{fill_type:"full",send_ack:!0,fills_per_order:1,partial_fill_pct_min:50,partial_fill_pct_max:99,price_variance_ticks:0},order_template_id:null,exec_template_id:null,auto_expiry:!0,expiry_offset:1,expiry_unit:"days",gen_rules:{}}},jW=["loopback","manager","client","venue"];function ov(e,t){return t?Math.round(e/t*1e3):0}function SW({onStarted:e}){var mi,pi;const t=X(N=>N.sessions),[r,n]=h.useState(sv),[a,i]=h.useState(sv.payload.order.symbols.join(", ")),[s,o]=h.useState(!1),[c,u]=h.useState(""),[f,d]=h.useState(!1),[m,p]=h.useState([]),[v,y]=h.useState(""),[g,b]=h.useState([]),w=Object.values(t),x=r.mode==="loopback"||r.mode==="manager"||r.mode==="client",j=r.mode==="loopback"||r.mode==="manager"||r.mode==="venue",_=r.amend??Lc,S=r.cancel??Fc,O=r.client_session_id?t[r.client_session_id]:void 0,P=r.venue_session_id?t[r.venue_session_id]:void 0,E=!x||(O==null?void 0:O.status)==="LOGGED_ON",k=!j||(P==null?void 0:P.status)==="LOGGED_ON",T=(!x||!!r.client_session_id)&&(!j||!!r.venue_session_id),C=(()=>{if(!r.name.trim())return"Name is required";const N=r.rate;if(N.max_burst_multiplier<1)return"Max burst multiplier must be ≥ 1";if(r.test.scenario_timeout_ms<1)return"Order timeout must be > 0";if(r.test.max_pending<1)return"Max pending must be > 0";if(x){if(N.orders_per_window<1||N.order_window_ms<1)return"Order rate must be > 0";if(a.split(",").map(et=>et.trim()).filter(Boolean).length===0)return"At least one symbol is required";const Z=r.payload.order;if(Z.quantity_min<1||Z.quantity_max<1||Z.quantity_min>Z.quantity_max)return"Check quantity min/max";if(Z.price_min<=0||Z.price_max<=0||Z.price_min>Z.price_max)return"Check price min/max";if(_.enabled){if(_.every_n_fills<1)return"Amend every-n-fills must be ≥ 1";if(_.amend_ratio<0||_.amend_ratio>1)return"Amend ratio must be between 0 and 1";if(_.max_per_order<0)return"Max amends per order must be ≥ 0";if(_.price_offset<=0)return"Amend price offset must be > 0";if((_.amend_wait_ms??2e3)<1)return"Amend wait must be > 0"}if(S.enabled){if(S.after_fills<0)return"Cancel after-fills must be ≥ 0";if(S.cancel_ratio<0||S.cancel_ratio>1)return"Cancel ratio must be between 0 and 1";if(S.cxl_rej_reason<0)return"Cancel reject reason must be ≥ 0"}}if(j){if(N.fills_per_window<1||N.fill_window_ms<1)return"Fill rate must be > 0";if(r.payload.fill.fills_per_order<1){if(r.payload.fill.fills_per_order!==0)return"Fills per order must be ≥ 0";if(!S.enabled)return"Fills per order 0 (ack-only) requires Cancels enabled";if(S.after_fills>0)return"Cancel after-fills must be 0 (on ack) when fills per order is 0"}if(N.fill_ratio<0||N.fill_ratio>1)return"Fill ratio must be between 0 and 1"}return null})(),W=T&&E&&k&&!C&&!f;async function L(){try{p(await G.perf.configs.list())}catch{}}h.useEffect(()=>{L()},[]),h.useEffect(()=>{G.templates.list().then(b).catch(()=>{})},[]);const H=g.filter(N=>N.msg_type==="D"),I=g.filter(N=>N.msg_type==="8"),R=!!r.payload.order_template_id,z=!!r.payload.exec_template_id,U=((mi=H.find(N=>N.id===r.payload.order_template_id))==null?void 0:mi.name)??"selected",J=((pi=I.find(N=>N.id===r.payload.exec_template_id))==null?void 0:pi.name)??"selected",ie=N=>n(Z=>({...Z,...N})),ee=N=>n(Z=>({...Z,rate:{...Z.rate,...N}})),ve=N=>n(Z=>({...Z,test:{...Z.test,...N}})),ce=N=>n(Z=>({...Z,amend:{...Lc,...Z.amend,...N}})),Pe=N=>n(Z=>({...Z,cancel:{...Fc,...Z.cancel,...N}})),$=N=>n(Z=>({...Z,payload:{...Z.payload,order:{...Z.payload.order,...N}}})),ae=N=>n(Z=>({...Z,payload:{...Z.payload,fill:{...Z.payload.fill,...N}}})),te=N=>n(Z=>({...Z,payload:{...Z.payload,...N}})),[F,B]=h.useState([]),Q=N=>{B(N),te({gen_rules:Object.fromEntries(N.filter(Z=>Z.tag.trim()).map(Z=>[Z.tag.trim(),Z.value]))})},D=(N,Z)=>Q(F.map((et,Rr)=>Rr===N?{...et,...Z}:et));function Ee(){const N=a.split(",").map(Z=>Z.trim()).filter(Boolean);return{...r,client_session_id:x?r.client_session_id:null,venue_session_id:j?r.venue_session_id:null,payload:{...r.payload,order:{...r.payload.order,symbols:N}}}}async function le(){u(""),d(!0);try{const{run_id:N}=await G.perf.runs.create(Ee());e(N)}catch(N){u(N.message??"Failed to start run")}finally{d(!1)}}async function Re(){u("");try{await G.perf.configs.save(Ee()),await L()}catch(N){u(N.message??"Failed to save config")}}async function $e(N){if(y(N),!!N){u("");try{const Z=await G.perf.configs.get(N);n(Z),i(Z.payload.order.symbols.join(", ")),B(Object.entries(Z.payload.gen_rules??{}).map(([et,Rr])=>({tag:et,value:Rr})))}catch(Z){u(Z.message??"Failed to load config")}}}async function Nt(){if(v&&window.confirm("Delete this saved config?"))try{await G.perf.configs.delete(v),y(""),await L()}catch(N){u(N.message??"Failed to delete config")}}const er=N=>{const Z=t[N];return Z?`${Z.display_name||`${Z.sender_comp_id} → ${Z.target_comp_id}`} (${Z.status})`:N},xn=w.filter(N=>N.session_role!=="venue"),hi=w.filter(N=>N.session_role!=="client");return l.jsxs("div",{className:"perf-form",children:[c&&l.jsx("div",{className:"error-msg",children:c}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Saved configs"}),l.jsxs("div",{className:"perf-field-row",children:[l.jsxs("select",{value:v,onChange:N=>$e(N.target.value),children:[l.jsx("option",{value:"",children:"(load a saved config…)"}),m.map(N=>l.jsxs("option",{value:N.config_id,children:[N.name," — ",N.mode]},N.config_id))]}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:Re,children:"Save current"}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:Nt,disabled:!v,children:"Delete"})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Run"}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Name"}),l.jsx("input",{value:r.name,onChange:N=>ie({name:N.target.value})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Mode"}),l.jsx("select",{value:r.mode,onChange:N=>ie({mode:N.target.value}),children:jW.map(N=>l.jsx("option",{value:N,children:N},N))})]}),x&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Client session"}),l.jsxs("select",{value:r.client_session_id??"",onChange:N=>ie({client_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),xn.map(N=>l.jsx("option",{value:N.session_id,children:er(N.session_id)},N.session_id))]}),O&&l.jsx(lv,{status:O.status})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue session"}),l.jsxs("select",{value:r.venue_session_id??"",onChange:N=>ie({venue_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),hi.map(N=>l.jsx("option",{value:N.session_id,children:er(N.session_id)},N.session_id))]}),P&&l.jsx(lv,{status:P.status})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Rate"}),l.jsxs("div",{className:"perf-field-grid",children:[x&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Orders / window"}),l.jsx("input",{type:"number",min:1,value:r.rate.orders_per_window,onChange:N=>ee({orders_per_window:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order window (ms)"}),l.jsx("input",{type:"number",min:1,value:r.rate.order_window_ms,onChange:N=>ee({order_window_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order dispatch"}),l.jsxs("select",{value:r.rate.dispatch??"smooth",onChange:N=>ee({dispatch:N.target.value}),children:[l.jsx("option",{value:"smooth",children:"smooth (evenly spaced)"}),l.jsx("option",{value:"burst",children:"burst (all at window start)"})]})]})]}),j&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fills / window"}),l.jsx("input",{type:"number",min:1,value:r.rate.fills_per_window,onChange:N=>ee({fills_per_window:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill window (ms)"}),l.jsx("input",{type:"number",min:1,value:r.rate.fill_window_ms,onChange:N=>ee({fill_window_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill ratio"}),l.jsx("input",{type:"number",min:0,max:1,step:.05,value:r.rate.fill_ratio,onChange:N=>ee({fill_ratio:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill dispatch"}),l.jsxs("select",{value:r.rate.fill_dispatch??"smooth",onChange:N=>ee({fill_dispatch:N.target.value}),children:[l.jsx("option",{value:"smooth",children:"smooth (evenly spaced)"}),l.jsx("option",{value:"burst",children:"burst (all at window start)"})]})]})]})]}),l.jsxs("div",{className:"perf-hint",children:[x&&l.jsxs(l.Fragment,{children:["≈ ",ov(r.rate.orders_per_window,r.rate.order_window_ms).toLocaleString()," orders/s"]}),x&&j&&" · ",j&&l.jsxs(l.Fragment,{children:["≈ ",ov(r.rate.fills_per_window,r.rate.fill_window_ms).toLocaleString()," fills/s"]})," ",r.rate.dispatch==="burst"?l.jsxs(l.Fragment,{children:["Burst: ",r.rate.orders_per_window.toLocaleString()," orders fire back-to-back every ",r.rate.order_window_ms,"ms, then silence — sawtooth load."]}):l.jsx(l.Fragment,{children:"Smaller windows smooth the injection."})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Test"}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Duration (s, 0 = no time limit)"}),l.jsx("input",{type:"number",min:0,value:r.test.duration,onChange:N=>ve({duration:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max orders (0 = no count limit)"}),l.jsx("input",{type:"number",min:0,value:r.test.max_orders,onChange:N=>ve({max_orders:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order timeout (ms)"}),l.jsx("input",{type:"number",min:1,step:1e3,value:r.test.scenario_timeout_ms,onChange:N=>ve({scenario_timeout_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"On saturation"}),l.jsxs("select",{value:r.test.on_saturation,onChange:N=>ve({on_saturation:N.target.value}),children:[l.jsx("option",{value:"pause",children:"pause (preserves latency accuracy)"}),l.jsx("option",{value:"drop_oldest",children:"drop_oldest"})]})]})]}),x&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.test.record_latency,onChange:N=>ve({record_latency:N.target.checked})}),"Record per-message latency — one CSV row per message crossing (orders, amends, cancels + acks, fills, replies); enables the per-message latency charts (one-way / round-trip). Downloads as csv.gz after the run."]}),l.jsxs("div",{className:"perf-hint",children:["An order not fully filled within the order timeout is counted ",l.jsx("strong",{children:"Lost"}),". Raise it if rate-limited fills are being flagged lost on full-fill runs."]})]}),x&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Amends (35=G)"}),l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:_.enabled,onChange:N=>ce({enabled:N.target.checked})}),"Amend working orders — price-only OrderCancelReplaceRequest fired every nth fill"]}),_.enabled&&l.jsxs(l.Fragment,{children:[l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Every n fills"}),l.jsx("input",{type:"number",min:1,value:_.every_n_fills,onChange:N=>ce({every_n_fills:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Amend ratio (orders)"}),l.jsx("input",{type:"number",min:0,max:1,step:.05,value:_.amend_ratio,onChange:N=>ce({amend_ratio:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max amends / order (0 = no cap)"}),l.jsx("input",{type:"number",min:0,value:_.max_per_order,onChange:N=>ce({max_per_order:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Price offset (±)"}),l.jsx("input",{type:"number",min:1e-4,step:.01,value:_.price_offset,onChange:N=>ce({price_offset:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Amend wait (ms)"}),l.jsx("input",{type:"number",min:1,step:100,value:_.amend_wait_ms??2e3,onChange:N=>ce({amend_wait_ms:Number(N.target.value)})})]})]}),j&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:_.pending_replace,onChange:N=>ce({pending_replace:N.target.checked})}),"Pending Replace — venue replies 150=E before 150=5 (two ExecReports per amend)"]}),l.jsx("div",{className:"perf-hint",children:"Amends fire only while the order is still working — never on the final fill, so use partial fills or fills per order > 1. The 35=G keeps the order's correlation id (376); amend rate ≈ fills/s ÷ n. Latency is measured amend-2-replaced (150=5). The venue holds an order's remaining fills at each trigger point (up to Amend wait) so the replace is answered while the order is working — with real CumQty — and post-replace fills go out at the amended price."}),j&&r.payload.fill.fill_type==="full"&&r.payload.fill.fills_per_order===1&&l.jsxs("div",{className:"perf-hint",children:["⚠ With a single full fill per order there is never a working order to amend — raise ",l.jsx("strong",{children:"Fills per order"})," or use ",l.jsx("strong",{children:"partial"})," fill type."]})]})]}),x&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Cancels (35=F)"}),l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:S.enabled,onChange:N=>Pe({enabled:N.target.checked})}),"Cancel orders — one OrderCancelRequest per order, on the ack or the nth fill"]}),S.enabled&&l.jsxs(l.Fragment,{children:[l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"After fills (0 = on ack)"}),l.jsx("input",{type:"number",min:0,value:S.after_fills,onChange:N=>Pe({after_fills:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Cancel ratio (orders)"}),l.jsx("input",{type:"number",min:0,max:1,step:.05,value:S.cancel_ratio,onChange:N=>Pe({cancel_ratio:Number(N.target.value)})})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue response"}),l.jsxs("select",{value:S.venue_response,onChange:N=>Pe({venue_response:N.target.value}),children:[l.jsx("option",{value:"reject",children:"Reject — 35=9 OrderCancelReject"}),l.jsx("option",{value:"cancel",children:"Accept — 150=4 Canceled"})]})]}),j&&S.venue_response==="reject"&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"CxlRejReason (102)"}),l.jsx("input",{type:"number",min:0,value:S.cxl_rej_reason,onChange:N=>Pe({cxl_rej_reason:Number(N.target.value)})})]})]}),l.jsxs("div",{className:"perf-hint",children:["The 35=F keeps the order's correlation id (376) and routing/identity tags; latency is measured cancel-2-reply (35=9 or 150=4). For the pure order > ack > cancel > reject sequence set ",l.jsx("strong",{children:"Fills per order"})," to 0 (ack-only orders — the reply is then what completes each order)."]})]})]}),x&&l.jsxs("div",{className:`perf-form-section ${R?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Order payload",R&&l.jsx("span",{className:"perf-layer-badge",children:"values for template"})]}),R&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Order template ",l.jsx("strong",{children:U})," defines the message shape. These fields supply the values it references via ",l.jsx("code",{children:"{symbol}"})," ",l.jsx("code",{children:"{side}"})," ",l.jsx("code",{children:"{qty}"})," ",l.jsx("code",{children:"{price}"})," tokens. Value generators (",l.jsx("code",{children:"uuid()"}),", ",l.jsx("code",{children:"random_str(n)"}),", ",l.jsx("code",{children:"random_int(a,b)"}),","," ",l.jsx("code",{children:"seq(width)"}),", ",l.jsx("code",{children:"timestamp()"}),", ",l.jsx("code",{children:"date()"}),") are evaluated fresh per order and mix with text as ",l.jsx("code",{children:"ORD-{seq(8)}"}),"; ClOrdID(11) is always made unique per order. ",l.jsx("strong",{children:"Order type"})," is taken from the template."]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Symbols (comma-separated)"}),l.jsx("input",{value:a,onChange:N=>i(N.target.value)})]}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Side"}),l.jsxs("select",{value:r.payload.order.side,onChange:N=>$({side:N.target.value}),children:[l.jsx("option",{value:"alternate",children:"alternate"}),l.jsx("option",{value:"fixed_buy",children:"fixed_buy"}),l.jsx("option",{value:"fixed_sell",children:"fixed_sell"}),l.jsx("option",{value:"random",children:"random"})]})]}),l.jsxs("label",{className:`perf-field ${R?"perf-field-overridden":""}`,children:[l.jsxs("span",{children:["Order type ",R&&l.jsx("em",{className:"perf-field-tag",children:"from template"})]}),l.jsxs("select",{value:r.payload.order.ord_type,disabled:R,onChange:N=>$({ord_type:N.target.value}),children:[l.jsx("option",{value:"limit",children:"limit"}),l.jsx("option",{value:"market",children:"market"})]})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Qty min"}),l.jsx("input",{type:"number",min:1,value:r.payload.order.quantity_min,onChange:N=>$({quantity_min:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Qty max"}),l.jsx("input",{type:"number",min:1,value:r.payload.order.quantity_max,onChange:N=>$({quantity_max:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Price min"}),l.jsx("input",{type:"number",min:0,step:.01,value:r.payload.order.price_min,onChange:N=>$({price_min:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Price max"}),l.jsx("input",{type:"number",min:0,step:.01,value:r.payload.order.price_max,onChange:N=>$({price_max:Number(N.target.value)})})]})]})]}),x&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Send options"}),l.jsxs("div",{className:"perf-field-row",children:[l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.payload.auto_expiry??!0,onChange:N=>te({auto_expiry:N.target.checked})}),"Auto expiry (tags 126 / 432, only if present)"]}),(r.payload.auto_expiry??!0)&&l.jsxs(l.Fragment,{children:[l.jsx("input",{type:"number",min:0,style:{width:64},value:r.payload.expiry_offset??1,onChange:N=>te({expiry_offset:Number(N.target.value)})}),l.jsxs("select",{value:r.payload.expiry_unit??"days",onChange:N=>te({expiry_unit:N.target.value}),children:[l.jsx("option",{value:"minutes",children:"minutes"}),l.jsx("option",{value:"hours",children:"hours"}),l.jsx("option",{value:"days",children:"days"})]}),l.jsx("span",{children:"into the future"})]})]}),F.map((N,Z)=>l.jsxs("div",{className:"perf-field-row",children:[l.jsx("input",{style:{width:72},placeholder:"tag",value:N.tag,onChange:et=>D(Z,{tag:et.target.value.replace(/[^0-9]/g,"")})}),l.jsx("span",{children:"="}),l.jsx("input",{style:{flex:1},placeholder:"uuid() / seq(8) / ORD-{seq(8)} / literal",value:N.value,onChange:et=>D(Z,{value:et.target.value})}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:()=>Q(F.filter((et,Rr)=>Rr!==Z)),children:"✕"})]},Z)),l.jsx("div",{className:"perf-field-row",children:l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Q([...F,{tag:"",value:""}]),children:"+ Add tag rule"})}),l.jsxs("div",{className:"perf-hint",children:["Rules set the tag on every order/scenario message (insert or replace). Generators: ",l.jsx("code",{children:"uuid()"})," · ",l.jsx("code",{children:"random_str(n)"})," · ",l.jsx("code",{children:"random_int(a,b)"})," ·"," ",l.jsx("code",{children:"seq(width[,start])"})," · ",l.jsx("code",{children:"timestamp()"})," · ",l.jsx("code",{children:"date()"})," — mix with text as ",l.jsx("code",{children:"ORD-{seq(8)}"}),". ClOrdID(11) always stays unique per order."]})]}),j&&l.jsxs("div",{className:`perf-form-section ${z?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Fill payload",z&&l.jsx("span",{className:"perf-layer-badge",children:"behavior for template"})]}),z&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Exec template ",l.jsx("strong",{children:J})," reshapes each report (custom tags). These still control fill ",l.jsx("strong",{children:"behavior"})," — how many reports, the fill ratio, and the ack — while the engine sets the standard exec tags."]}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill type"}),l.jsxs("select",{value:r.payload.fill.fill_type,onChange:N=>ae({fill_type:N.target.value}),children:[l.jsx("option",{value:"full",children:"full"}),l.jsx("option",{value:"partial",children:"partial"}),l.jsx("option",{value:"random",children:"random"})]})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fills per order (0 = ack-only)"}),l.jsx("input",{type:"number",min:0,value:r.payload.fill.fills_per_order,onChange:N=>ae({fills_per_order:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.payload.fill.send_ack,onChange:N=>ae({send_ack:N.target.checked})}),l.jsx("span",{children:"Send ack before fills (35=8|150=0|39=0)"})]}),r.payload.fill.fill_type!=="full"&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Partial % min"}),l.jsx("input",{type:"number",min:1,max:100,value:r.payload.fill.partial_fill_pct_min,onChange:N=>ae({partial_fill_pct_min:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Partial % max"}),l.jsx("input",{type:"number",min:1,max:100,value:r.payload.fill.partial_fill_pct_max,onChange:N=>ae({partial_fill_pct_max:Number(N.target.value)})})]})]})]})]}),(x||j)&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Templates (optional)"}),l.jsxs("div",{className:"perf-field-grid",children:[x&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order template"}),l.jsxs("select",{value:r.payload.order_template_id??"",onChange:N=>te({order_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),H.map(N=>l.jsx("option",{value:N.id,children:N.name},N.id))]})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Exec report template"}),l.jsxs("select",{value:r.payload.exec_template_id??"",onChange:N=>te({exec_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),I.map(N=>l.jsx("option",{value:N.id,children:N.name},N.id))]})]})]}),l.jsx("div",{className:"perf-hint",children:"Optional FIX templates from your Templates library shape the order / execution report (custom & venue-specific tags). Standard tags (correlation, OrderQty, CumQty, ExecType…) are always set by the engine. Leave as built-in for the default messages."})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsxs("button",{className:"btn btn-sm btn-ghost",onClick:()=>o(N=>!N),children:[s?"▾":"▸"," Advanced"]}),s&&l.jsxs("div",{className:"perf-field-grid",style:{marginTop:8},children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Correlation tag"}),l.jsx("input",{type:"number",min:1,value:r.correlation_tag,onChange:N=>ie({correlation_tag:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Exec ID tag"}),l.jsx("input",{type:"number",min:1,value:r.exec_id_tag,onChange:N=>ie({exec_id_tag:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max pending"}),l.jsx("input",{type:"number",min:1,value:r.test.max_pending,onChange:N=>ve({max_pending:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.rate.allow_burst,onChange:N=>ee({allow_burst:N.target.checked})}),l.jsx("span",{children:"Allow burst"})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max burst multiplier"}),l.jsx("input",{type:"number",min:1,step:.5,value:r.rate.max_burst_multiplier,onChange:N=>ee({max_burst_multiplier:Number(N.target.value)})})]})]})]}),l.jsxs("div",{className:"perf-form-actions",children:[C?l.jsx("span",{className:"perf-not-ready",children:C}):T&&!(E&&k)?l.jsxs("span",{className:"perf-not-ready",children:["Selected session",x&&j?"s":""," must be LOGGED_ON before starting."]}):null,l.jsx("button",{className:"btn btn-primary",onClick:le,disabled:!W,title:T?C||(E&&k?void 0:"Session not logged on yet"):"Select the required session(s)",children:f?"Starting…":"▶ Start run"})]})]})}function lv({status:e}){const t=e==="LOGGED_ON";return l.jsxs("span",{className:`perf-status-chip ${t?"ok":"warn"}`,children:[t?"● ":"○ ",e]})}const cv=600,_W=5e3,OW=new Set(["completed","stopped","error"]);function PW(e){const[t,r]=h.useState(null),[n,a]=h.useState([]),[i,s]=h.useState(!1);return h.useEffect(()=>{if(r(null),a([]),s(!1),!e)return;let o=!1,c=null,u,f=0,d=!1;async function m(){if(o||d||!e)return;let v;try{({ticket:v}=await G.perf.runs.ticket(e))}catch{p();return}if(o)return;const g=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/api/perf/runs/${e}/live?ticket=${v}`;c=new WebSocket(g),c.onopen=()=>{o||(f=0,s(!0))},c.onmessage=b=>{let w;try{w=JSON.parse(b.data)}catch{return}r(w),a(x=>{var S,O,P,E,k,T,C,W,L,H,I,R,z,U,J,ie;const j={t:w.elapsed_s,ops:((S=w.client)==null?void 0:S.ops_live)??0,fps:((O=w.venue)==null?void 0:O.fps_live)??0,aps:((P=w.client)==null?void 0:P.aps_live)??0,cps:((E=w.client)==null?void 0:E.cps_live)??0,crps:((k=w.client)==null?void 0:k.crps_live)??0,ccps:((T=w.client)==null?void 0:T.ccps_live)??0,mco:((C=w.client)==null?void 0:C.msgs_out_live)??0,mci:((W=w.client)==null?void 0:W.msgs_in_live)??0,mvo:((L=w.venue)==null?void 0:L.msgs_out_live)??0,mvi:((H=w.venue)==null?void 0:H.msgs_in_live)??0,tps:w.client?w.client.msgs_out_live+w.client.msgs_in_live:(((I=w.venue)==null?void 0:I.msgs_out_live)??0)+(((R=w.venue)==null?void 0:R.msgs_in_live)??0),p50:(((z=w.response_latency)==null?void 0:z.p50_us)??0)/1e3,p99:(((U=w.response_latency)==null?void 0:U.p99_us)??0)/1e3,fc50:(((J=w.fill_completion_latency)==null?void 0:J.p50_us)??0)/1e3,fc99:(((ie=w.fill_completion_latency)==null?void 0:ie.p99_us)??0)/1e3},_=[...x,j];return _.length>cv?_.slice(-cv):_}),OW.has(w.status)&&(d=!0,c==null||c.close())},c.onclose=()=>{o||(s(!1),d||p())},c.onerror=()=>{c==null||c.close()}}function p(){if(o||d)return;f+=1;const v=Math.min(_W,500*2**Math.min(f,4));u=setTimeout(m,v)}return m(),()=>{o=!0,clearTimeout(u),c==null||c.close()}},[e]),{latest:t,series:n,connected:i}}const Ws="img-export-exclude";async function NW(e,t,r){const{toPng:n,toJpeg:a}=await Uv(async()=>{const{toPng:u,toJpeg:f}=await import("./index-CyNOPa0n.js");return{toPng:u,toJpeg:f}},[]),i=getComputedStyle(document.body).backgroundColor||"#111",o=await(r==="png"?n:a)(e,{backgroundColor:i,pixelRatio:2,quality:.95,filter:u=>!(u instanceof HTMLElement&&u.classList.contains(Ws))}),c=document.createElement("a");c.href=o,c.download=t,c.click()}function CW(e,t){const r=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");return`${e}-${r}.${t}`}const Rl=new Set(["completed","stopped","error"]);function Wt(e){return e?(e/1e3).toFixed(3):"—"}function Xw(e,t){return t<=0?"—":(e/t).toFixed(2)}function Di({title:e,sub:t,stats:r}){return l.jsxs("div",{className:"perf-lat-card",children:[l.jsxs("div",{className:"perf-lat-title",children:[e,t&&l.jsx("span",{className:"perf-lat-sub",children:t})]}),l.jsx("div",{className:"perf-lat-grid",children:["p50_us","p95_us","p99_us","max_us"].map(n=>l.jsxs("div",{className:"perf-lat-cell",children:[l.jsx("span",{className:"perf-lat-val",children:Wt(r[n])}),l.jsxs("span",{className:"perf-lat-lbl",children:[n.replace("_us","")," ms"]})]},n))})]})}function EW({runId:e}){var _,S,O;const{latest:t,series:r,connected:n}=PW(e),[a,i]=h.useState(null),[s,o]=h.useState(!1),[c,u]=h.useState("direction"),f=h.useRef(null);h.useEffect(()=>{let P=!0;i(null);let E;const k=async()=>{try{const T=await G.perf.runs.get(e);if(!P)return;i(T),Rl.has(T.status)&&clearInterval(E)}catch{}};return E=setInterval(k,2e3),k(),()=>{P=!1,clearInterval(E)}},[e]);const d=t?Rl.has(t.status):a?Rl.has(a.status):!1,m=!d;async function p(){try{await G.perf.runs.stop(e)}catch{}}async function v(P){if(!(!f.current||s)){o(!0);try{const E=P==="png"?"png":"jpg";await NW(f.current,CW(`perf-${e.slice(0,8)}`,E),P)}catch(E){console.error("image export failed",E)}finally{o(!1)}}}const y=t==null?void 0:t.client,g=t==null?void 0:t.venue,b=t==null?void 0:t.errors,w=(b==null?void 0:b.saturated)||(t==null?void 0:t.status)==="saturated",x=t==null?void 0:t.rate_config,j=[];return y&&(j.push({label:"orders/s",value:Math.round(y.ops_live).toLocaleString()}),(x==null?void 0:x.orders_per_window)!=null&&(x==null?void 0:x.order_window_ms)!=null&&j.push({label:"order rate (cfg)",value:`${x.orders_per_window.toLocaleString()} / ${x.order_window_ms}ms`}),j.push({label:"orders sent",value:y.orders_sent.toLocaleString()},{label:"pending",value:y.pending.toLocaleString()},{label:"dropped",value:y.dropped.toLocaleString()}),(y.amends_sent>0||y.aps_live>0)&&(j.push({label:"amends/s",value:Math.round(y.aps_live).toLocaleString()},{label:"amends",value:y.amends_sent.toLocaleString()},{label:"replaces",value:y.replaces_received.toLocaleString()}),y.amends_lost>0&&j.push({label:"amends lost",value:y.amends_lost.toLocaleString()})),y.amend_rejects>0&&j.push({label:"amend rejects (35=9)",value:y.amend_rejects.toLocaleString()}),(y.cancels_sent??0)>0&&(j.push({label:"cancels (35=F)",value:y.cancels_sent.toLocaleString()}),(y.cancel_rejects??0)>0&&j.push({label:"cancel rejects (35=9)",value:y.cancel_rejects.toLocaleString()}),(y.cancels_canceled??0)>0&&j.push({label:"canceled (150=4)",value:y.cancels_canceled.toLocaleString()}),(y.cancels_lost??0)>0&&j.push({label:"cancels lost",value:y.cancels_lost.toLocaleString()}))),g&&(j.push({label:"fills/s",value:Math.round(g.fps_live).toLocaleString()}),(x==null?void 0:x.fills_per_window)!=null&&(x==null?void 0:x.fill_window_ms)!=null&&j.push({label:"fill rate (cfg)",value:`${x.fills_per_window.toLocaleString()} / ${x.fill_window_ms}ms`}),j.push({label:"fills",value:g.fills_sent.toLocaleString()},{label:"fills/order",value:Xw(g.fills_sent,g.orders_received-g.unfilled)},{label:"fill ratio",value:g.fill_ratio.toFixed(3)},{label:"recv",value:g.orders_received.toLocaleString()},{label:"acks",value:g.acks_sent.toLocaleString()},{label:"unfilled",value:g.unfilled.toLocaleString()}),g.amends_received>0&&j.push({label:"amends recv",value:g.amends_received.toLocaleString()}),g.amends_uncorrelated>0&&j.push({label:"amends no-corr",value:g.amends_uncorrelated.toLocaleString()}),g.amend_waits_expired>0&&j.push({label:"amend waits expired",value:g.amend_waits_expired.toLocaleString()}),(g.cancels_received??0)>0&&j.push({label:"cancels recv",value:g.cancels_received.toLocaleString()}),(g.cancels_uncorrelated??0)>0&&j.push({label:"cancels no-corr",value:g.cancels_uncorrelated.toLocaleString()})),b&&j.push({label:"lost",value:b.lost_timeout.toLocaleString()},{label:"rejected",value:b.rejected.toLocaleString()}),l.jsxs("div",{className:"perf-dashboard",ref:f,children:[l.jsxs("div",{className:"perf-dash-header",children:[l.jsxs("div",{className:"perf-dash-status",children:[l.jsx("span",{className:`badge perf-state-${(t==null?void 0:t.status)??(a==null?void 0:a.status)??"pending"}`,children:(t==null?void 0:t.status)??(a==null?void 0:a.status)??"pending"}),t&&l.jsxs("span",{className:"perf-dash-elapsed",children:[Math.round(t.elapsed_s),"s",t.duration_s?` / ${t.duration_s}s`:""," · ",t.mode]}),l.jsx("span",{className:`perf-conn ${n?"on":"off"}`,children:n?"● live":"○ offline"})]}),l.jsxs("div",{className:`perf-dash-actions ${Ws}`,children:[m&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:p,children:"■ Stop"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>G.perf.runs.export(e,"messages"),children:"Export CSV"}),d&&l.jsx("button",{className:"btn btn-sm btn-ghost",title:"Per-message latency rows (requires record per-message latency)",onClick:()=>G.perf.runs.export(e,"latency"),children:"Latency CSV"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>G.perf.runs.export(e,"both"),children:"Export ZIP"}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:s,onClick:()=>v("png"),children:s?"…":"PNG"}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:s,onClick:()=>v("jpeg"),children:s?"…":"JPG"})]})]}),w&&l.jsxs("div",{className:"perf-saturated-banner",children:["⚠ SATURATED — injection paused to protect latency measurement.",y&&l.jsxs(l.Fragment,{children:[" Pending ",y.pending.toLocaleString(),", dropped ",y.dropped.toLocaleString(),"."]})]}),(_=a==null?void 0:a.warnings)==null?void 0:_.map((P,E)=>l.jsxs("div",{className:"perf-warning-banner",children:["⚠ ",P]},E)),!t&&!d&&l.jsx("div",{className:"loading-msg",children:"Waiting for first snapshot…"}),j.length>0&&l.jsx("div",{className:"stat-cards",children:j.map(({label:P,value:E})=>l.jsxs("div",{className:"stat-card",children:[l.jsx("span",{className:"stat-card-value",children:E}),l.jsx("span",{className:"stat-card-label",children:P})]},P))}),t&&l.jsxs("div",{className:"perf-lat-row",children:[l.jsx(Di,{title:"Response latency",sub:"order-2-ack",stats:t.response_latency}),l.jsx(Di,{title:"Fill-completion latency",sub:"order-2-last-fill",stats:t.fill_completion_latency}),((S=t.amend_latency)==null?void 0:S.count)>0&&l.jsx(Di,{title:"Amend latency",sub:"amend-2-replaced",stats:t.amend_latency}),((O=t.cancel_latency)==null?void 0:O.count)>0&&l.jsx(Di,{title:"Cancel latency",sub:"cancel-2-reply",stats:t.cancel_latency})]}),r.length>1&&l.jsxs(l.Fragment,{children:[l.jsx("div",{className:"analysis-section-title",children:"Throughput"}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),l.jsx(Ie,{type:"monotone",dataKey:"ops",name:"orders/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"fps",name:"fills/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.amends_sent)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"aps",name:"amends/s",stroke:"#c084fc",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancels_sent)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"cps",name:"cancels/s (35=F)",stroke:"#fb923c",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancel_rejects)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"crps",name:"cancel-rejects/s (35=9)",stroke:"#f43f5e",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancels_canceled)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"ccps",name:"canceled/s (150=4)",stroke:"#2dd4bf",dot:!1,isAnimationActive:!1})]})})}),l.jsxs("div",{className:"analysis-section-title perf-mps-title",children:[l.jsx("span",{children:"Messages/sec"}),l.jsxs("select",{className:Ws,value:c,onChange:P=>u(P.target.value),children:[l.jsx("option",{value:"direction",children:"by direction"}),l.jsx("option",{value:"leg",children:"per leg"})]})]}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),c==="direction"?l.jsxs(l.Fragment,{children:[l.jsx(Ie,{type:"monotone",dataKey:"tps",name:"total msgs/s",stroke:"#fbbf24",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"mco",name:"sent/s (requests)",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"mci",name:"received/s (replies)",stroke:"#a78bfa",dot:!1,isAnimationActive:!1})]}):l.jsxs(l.Fragment,{children:[l.jsx(Ie,{type:"monotone",dataKey:P=>P.mco+P.mci,name:"client session/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:P=>P.mvo+P.mvi,name:"venue session/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1})]})]})})}),l.jsx("div",{className:"analysis-section-title",children:"Response latency (ms)"}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),l.jsx(Ie,{type:"monotone",dataKey:"p50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"p99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})}),l.jsx("div",{className:"analysis-section-title",children:"Fill-completion latency (ms)"}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),l.jsx(Ie,{type:"monotone",dataKey:"fc50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"fc99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})})]}),d&&l.jsx(AW,{runId:e}),d&&(a==null?void 0:a.summary)&&l.jsx(kW,{status:a})]})}function AW({runId:e}){const[t,r]=h.useState("one_way"),[n,a]=h.useState("both"),[i,s]=h.useState("log"),[o,c]=h.useState(null),[u,f]=h.useState(null),[d,m]=h.useState(!0);if(h.useEffect(()=>{let v=!0,y=null;return m(!0),f(null),G.perf.runs.chart(e,{metric:t,agg:n,scale:i}).then(g=>{if(!v){URL.revokeObjectURL(g);return}y=g,c(g)}).catch(g=>{v&&f(g instanceof Error?g.message:String(g))}).finally(()=>{v&&m(!1)}),()=>{v=!1,y&&URL.revokeObjectURL(y)}},[e,t,n,i]),u&&/record_latency|No per-message|not found/i.test(u))return null;function p(){if(!o)return;const v=document.createElement("a");v.href=o,v.download=`${e}_latency.png`,v.click()}return l.jsxs("div",{className:`perf-chart-section ${Ws}`,children:[l.jsx("div",{className:"analysis-section-title",children:"Per-message latency"}),l.jsxs("div",{className:"perf-chart-controls",children:[l.jsxs("label",{children:["Latency ",l.jsxs("select",{value:t,onChange:v=>r(v.target.value),children:[l.jsx("option",{value:"one_way",children:"per-message (one-way)"}),l.jsx("option",{value:"round_trip",children:"round-trip"}),l.jsx("option",{value:"both",children:"both"})]})]}),l.jsxs("label",{children:["View ",l.jsxs("select",{value:n,onChange:v=>a(v.target.value),children:[l.jsx("option",{value:"both",children:"scatter + p50/p99/max"}),l.jsx("option",{value:"scatter",children:"scatter"}),l.jsx("option",{value:"percentile",children:"p50/p99/max"})]})]}),l.jsxs("label",{children:["Scale ",l.jsxs("select",{value:i,onChange:v=>s(v.target.value),children:[l.jsx("option",{value:"log",children:"log"}),l.jsx("option",{value:"linear",children:"linear"})]})]}),l.jsx("button",{className:"btn btn-sm",onClick:p,disabled:!o,children:"Download PNG"})]}),d&&l.jsx("div",{className:"perf-hint",children:"Rendering chart…"}),u&&!d&&l.jsx("div",{className:"error-msg",children:u}),o&&!d&&l.jsx("img",{src:o,alt:"Per-message latency by kind",className:"perf-chart-img"})]})}function kW({status:e}){const t=e.summary,r=[["Orders sent",t.orders_sent.toLocaleString()],["Acks sent",t.acks_sent.toLocaleString()],["Fills sent",t.fills_sent.toLocaleString()],["Responses",t.responses.toLocaleString()],["Completions (fully filled)",t.completions.toLocaleString()],["Fills per order (completed)",Xw(t.fills_sent,t.completions)],["Fill ratio (completed)",t.fill_ratio.toFixed(3)],["Lost / dropped / rejected",`${t.lost_timeout} / ${t.dropped} / ${t.rejected}`],["Response p50 / p99 (ms)",`${Wt(t.response_latency.p50_us)} / ${Wt(t.response_latency.p99_us)}`],["Response max (ms)",Wt(t.response_latency.max_us)],["Fill-completion p50 / p99 (ms)",`${Wt(t.fill_completion_latency.p50_us)} / ${Wt(t.fill_completion_latency.p99_us)}`]];return t.amends_sent&&(r.push(["Amends sent / replaced / rejected / lost",`${t.amends_sent} / ${t.replaces_received??0} / ${t.amend_rejects??0} / ${t.amends_lost??0}`],["Amends recv (venue) / no-corr / waits expired",`${t.amends_received??0} / ${t.amends_uncorrelated??0} / ${t.amend_waits_expired??0}`]),t.amend_latency&&r.push(["Amend p50 / p99 (ms)",`${Wt(t.amend_latency.p50_us)} / ${Wt(t.amend_latency.p99_us)}`])),t.cancels_sent&&(r.push(["Cancels sent / rejected (35=9) / canceled (150=4) / lost",`${t.cancels_sent} / ${t.cancel_rejects??0} / ${t.cancels_canceled??0} / ${t.cancels_lost??0}`],["Cancels recv (venue) / no-corr",`${t.cancels_received??0} / ${t.cancels_uncorrelated??0}`]),t.cancel_latency&&r.push(["Cancel p50 / p99 (ms)",`${Wt(t.cancel_latency.p50_us)} / ${Wt(t.cancel_latency.p99_us)}`])),l.jsxs("div",{className:"perf-summary-card",children:[l.jsxs("div",{className:"perf-section-title",children:["Run summary — ",e.status]}),l.jsx("table",{className:"perf-summary-table",children:l.jsx("tbody",{children:r.map(([n,a])=>l.jsxs("tr",{children:[l.jsx("td",{children:n}),l.jsx("td",{className:"mono",children:a})]},n))})})]})}const IW=new Set(["completed","stopped","error"]);function TW(e){if(!e)return"—";try{return new Date(e*1e3).toLocaleTimeString()}catch{return"—"}}function MW({activeRunId:e,onSelect:t,refreshKey:r}){const[n,a]=h.useState([]);h.useEffect(()=>{let s=!0;const o=()=>G.perf.runs.list().then(u=>{s&&a(u)}).catch(()=>{});o();const c=setInterval(o,2e3);return()=>{s=!1,clearInterval(c)}},[r]);async function i(s,o){s.stopPropagation();try{await G.perf.runs.stop(o)}catch{}}return n.length===0?l.jsx("div",{className:"perf-runlist-empty",children:"No runs yet. Configure and start one."}):l.jsxs("table",{className:"perf-runlist",children:[l.jsx("thead",{children:l.jsxs("tr",{children:[l.jsx("th",{children:"Name"}),l.jsx("th",{children:"Mode"}),l.jsx("th",{children:"Status"}),l.jsx("th",{children:"Started"}),l.jsx("th",{children:"Elapsed"}),l.jsx("th",{children:"Orders"}),l.jsx("th",{children:"Fill"}),l.jsx("th",{})]})}),l.jsx("tbody",{children:n.map(s=>l.jsxs("tr",{className:s.run_id===e?"perf-run-active":"",onClick:()=>t(s.run_id),children:[l.jsx("td",{children:s.name}),l.jsx("td",{children:s.mode}),l.jsx("td",{children:l.jsx("span",{className:`badge perf-state-${s.status}`,children:s.status})}),l.jsx("td",{children:TW(s.started_at)}),l.jsxs("td",{className:"mono",children:[Math.round(s.elapsed_s),"s"]}),l.jsx("td",{className:"mono",children:s.summary?s.summary.orders_sent.toLocaleString():"—"}),l.jsx("td",{className:"mono",children:s.summary?s.summary.fill_ratio.toFixed(2):"—"}),l.jsx("td",{className:"perf-runlist-actions",children:!IW.has(s.status)&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:o=>i(o,s.run_id),children:"Stop"})})]},s.run_id))})]})}const $l=200,Tn=(e,t,r=120)=>({field:e,headerName:t,width:r,type:"numericColumn",valueFormatter:n=>n.value==null?"":Number(n.value).toLocaleString()}),Bc=(e,t)=>({field:e,headerName:t,width:150,type:"numericColumn",valueFormatter:r=>r.value==null?"":`${Math.round(Number(r.value)).toLocaleString()}`}),DW=[{field:"clordid",headerName:"ClOrdID",width:150},{field:"corr_id",headerName:"Corr ID",width:150},{field:"msg_type",headerName:"Type",width:80},{field:"symbol",headerName:"Symbol",width:90},{field:"side",headerName:"Side",width:70},Tn("qty","Qty",90),Tn("price","Price",100),Bc("response_latency_us","Resp µs"),Bc("fill_latency_us","Fill µs"),Tn("fill_qty","Fill qty",100),Tn("fill_price","Fill price",110),{field:"status",headerName:"Status",width:110,cellStyle:e=>e.value==="lost"||e.value==="dropped"?{color:"#facc15",fontWeight:"bold"}:void 0}],RW=[{field:"scenario_name",headerName:"Scenario",flex:1,minWidth:160},Bc("latency_us","Latency µs"),Tn("msg_count","Msgs",90),Tn("fill_ratio","Fill ratio",110),{field:"status",headerName:"Status",width:110,cellStyle:e=>e.value==="lost"||e.value==="dropped"?{color:"#facc15",fontWeight:"bold"}:void 0}];function $W({runId:e}){const t=X(g=>g.theme),[r,n]=h.useState("messages"),[a,i]=h.useState(0),[s,o]=h.useState([]),[c,u]=h.useState(0),[f,d]=h.useState(!1);h.useEffect(()=>{i(0)},[r,e]),h.useEffect(()=>{let g=!0;return d(!0),(r==="messages"?G.perf.runs.messages:G.perf.runs.scenarios)(e,{limit:$l,offset:a*$l}).then(w=>{g&&(o(w.items),u(w.total))}).catch(()=>{g&&(o([]),u(0))}).finally(()=>{g&&d(!1)}),()=>{g=!1}},[e,r,a]);const m=h.useMemo(()=>r==="messages"?DW:RW,[r]),p=h.useMemo(()=>({sortable:!0,filter:!0,resizable:!0}),[]),v=Math.max(0,Math.ceil(c/$l)-1),y=t==="dark"?"ag-theme-quartz-dark":"ag-theme-quartz";return l.jsxs("div",{className:"perf-results",children:[l.jsxs("div",{className:"perf-results-toolbar",children:[l.jsxs("div",{className:"perf-results-tabs",children:[l.jsx("button",{className:`btn btn-sm ${r==="messages"?"btn-primary":"btn-ghost"}`,onClick:()=>n("messages"),children:"Messages"}),l.jsx("button",{className:`btn btn-sm ${r==="scenarios"?"btn-primary":"btn-ghost"}`,onClick:()=>n("scenarios"),children:"Scenarios"})]}),l.jsxs("div",{className:"perf-results-pager",children:[l.jsxs("span",{children:[c.toLocaleString()," rows"]}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:a<=0||f,onClick:()=>i(g=>Math.max(0,g-1)),children:"‹ Prev"}),l.jsxs("span",{children:["Page ",a+1," / ",v+1]}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:a>=v||f,onClick:()=>i(g=>Math.min(v,g+1)),children:"Next ›"})]})]}),l.jsx("div",{className:`${y} perf-results-grid`,children:l.jsx(zc,{rowData:s,columnDefs:m,defaultColDef:p,animateRows:!1,suppressCellFocus:!0})})]})}function LW(){const{theme:e,toggleTheme:t,branding:r,setSessions:n}=X(),a=tl(),i=r!=null&&r.prefix?`${r.prefix} FIXture`:"FIXture";Lv(),h.useEffect(()=>{G.sessions.list().then(n).catch(()=>{})},[]);const[s,o]=h.useState("configure"),[c,u]=h.useState(null),[f,d]=h.useState(0);function m(y){u(y),d(g=>g+1),o("live")}function p(y){u(y),o("live")}const v=[{id:"configure",label:"Configure"},{id:"live",label:"Live",disabled:!c},{id:"results",label:"Results",disabled:!c},{id:"history",label:"History"}];return l.jsxs("div",{className:"app perf-page",children:[l.jsxs("header",{className:"app-header",children:[l.jsx("img",{src:a,alt:i,className:"app-logo"}),l.jsx("div",{className:"perf-page-title",children:"Performance Testing"}),l.jsxs("div",{className:"header-right",children:[l.jsx(tn,{to:"/",className:"btn btn-sm btn-ghost",children:"← Sessions"}),l.jsx("button",{className:"btn-icon theme-toggle",onClick:t,title:"Toggle theme",children:e==="dark"?"☀":"🌙"})]})]}),l.jsx("div",{className:"perf-tabbar",children:v.map(y=>l.jsx("button",{className:`perf-tab${s===y.id?" active":""}`,disabled:y.disabled,onClick:()=>o(y.id),children:y.label},y.id))}),l.jsxs("div",{className:"perf-body",children:[s==="configure"&&l.jsx("div",{className:"perf-pane perf-pane-form",children:l.jsx(SW,{onStarted:m})}),s==="live"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(EW,{runId:c},c)}),s==="results"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx($W,{runId:c},c)}),s==="history"&&l.jsx("div",{className:"perf-pane",children:l.jsx(MW,{activeRunId:c,onSelect:p,refreshKey:f})})]})]})}function FW(){return X(t=>t.token)?l.jsx(Pv,{}):l.jsx(Gi,{to:"/login",replace:!0})}function BW(){const e=X(t=>t.user);return e?e.role!=="platform_admin"?l.jsx(Gi,{to:"/",replace:!0}):l.jsx(Pv,{}):l.jsx(Gi,{to:"/login",replace:!0})}function zW(){const e=X(t=>t.setBranding);h.useEffect(()=>{fetch("/api/branding").then(t=>t.json()).then(t=>{e(t),Kw(t)}).catch(()=>{})},[])}function WW(){const e=fn(),t=X(r=>r.theme);return zW(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{fetch("/api/setup/status").then(r=>r.json()).then(r=>{r.setup_required&&e("/setup",{replace:!0})}).catch(()=>{})},[]),l.jsxs(uS,{children:[l.jsx(zt,{path:"/setup",element:l.jsx(pW,{})}),l.jsx(zt,{path:"/login",element:l.jsx(mW,{})}),l.jsx(zt,{path:"/register",element:l.jsx(vW,{})}),l.jsxs(zt,{element:l.jsx(FW,{}),children:[l.jsx(zt,{path:"/",element:l.jsx(hW,{})}),l.jsxs(zt,{element:l.jsx(BW,{}),children:[l.jsx(zt,{path:"/admin/users",element:l.jsx(wW,{})}),l.jsx(zt,{path:"/perf",element:l.jsx(LW,{})})]})]}),l.jsx(zt,{path:"*",element:l.jsx(Gi,{to:"/",replace:!0})})]})}Ll.createRoot(document.getElementById("root")).render(l.jsx(uv.StrictMode,{children:l.jsx(MS,{children:l.jsx(WW,{})})}));
|
|
102
|
+
}`;function fW({onClose:e}){const[t,r]=h.useState(""),[n,a]=h.useState(""),[i,s]=h.useState(!1);h.useEffect(()=>{G.specOverlay.get().then(c=>r(JSON.stringify(c,null,2))).catch(console.error)},[]);async function o(){a("");let c;try{c=JSON.parse(t)}catch{a("Invalid JSON");return}s(!0);try{await G.specOverlay.set(c),e()}catch(u){a(u instanceof Error?u.message:"Save failed")}finally{s(!1)}}return l.jsx("div",{className:"modal-overlay",onClick:e,children:l.jsxs("div",{className:"modal",style:{width:660,maxHeight:"85vh",display:"flex",flexDirection:"column"},onClick:c=>c.stopPropagation(),children:[l.jsxs("div",{className:"modal-header",children:[l.jsx("span",{children:"Spec Overlay — Custom Message Types"}),l.jsx("button",{className:"btn-icon",onClick:e,children:"✕"})]}),l.jsx("div",{style:{padding:"10px 16px 4px",fontSize:12,color:"var(--text2)"},children:"Define custom / proprietary message types (e.g. 35=U1). Fields reference standard tag numbers; use Custom Tags for non-standard tag names. Field names and types are resolved from the spec at runtime."}),l.jsx("div",{style:{flex:1,display:"flex",flexDirection:"column",padding:"8px 16px",minHeight:0},children:l.jsx("textarea",{style:{flex:1,minHeight:300,fontFamily:"var(--font)",fontSize:12,background:"var(--bg3)",color:"var(--text)",border:"1px solid var(--border)",borderRadius:4,padding:"8px 10px",resize:"none"},value:t,onChange:c=>r(c.target.value),spellCheck:!1,placeholder:dW})}),l.jsxs("div",{style:{padding:"0 16px 8px",fontSize:11,color:"var(--text2)"},children:["Schema: ",l.jsx("code",{children:'{ "messages": [{ "msg_type", "name", "category", "fields": [{ "tag", "required" }] }] }'})]}),n&&l.jsx("div",{className:"tpl-error",style:{margin:"0 16px 8px"},children:n}),l.jsxs("div",{className:"modal-footer",children:[l.jsx("button",{className:"btn btn-ghost",onClick:e,children:"Cancel"}),l.jsx("button",{className:"btn btn-primary",onClick:o,disabled:i,children:i?"Saving…":"Save overlay"})]})]})})}const Zw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgCgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGNpcmNsZSBjeD0iMjA3IiBjeT0iNzgiIHI9IjEwIiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjY1IiB4Mj0iMjQ4IiB5Mj0iNjUiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDAsIDE4MCwgMjAwKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMCwgMTgwLCAyMDApO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNztmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI5MSIgeDI9IjI0NCIgeTI9IjkxIiBzdHJva2U9IiMwMGI0YzgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjQ1IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigwLCAxODAsIDIwMCk7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC40NTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgoKICA8dGV4dCB4PSIyNzIiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtbW9ubykiIGZvbnQtc2l6ZT0iNjYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6NTBweDtmb250LXdlaWdodDozMDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byI+dHVyZTwvdGV4dD4KPC9zdmc+",qw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSIxMCIgZmlsbD0iIzFhM2E1YyIgc3R5bGU9ImZpbGw6cmdiKDI2LCA1OCwgOTIpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI2NSIgeDI9IjI0OCIgeTI9IjY1IiBzdHJva2U9IiMxYTNhNWMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigyNiwgNTgsIDkyKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMjYsIDU4LCA5Mik7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC43O2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjkxIiB4Mj0iMjQ0IiB5Mj0iOTEiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNDUiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDI2LCA1OCwgOTIpO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNDU7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJnF1b3Q7U2Vnb2UgVUkmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7Zm9udC13ZWlnaHQ6NDAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iLz4KCiAgPHRleHQgeD0iMjcyIiB5PSIxMDUiIGZvbnQtZmFtaWx5PSJ2YXIoLS1mb250LW1vbm8pIiBmb250LXNpemU9IjY2IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMWEzYTVjIiBsZXR0ZXItc3BhY2luZz0iLTEiIHN0eWxlPSJmaWxsOnJnYigyNiwgNTgsIDkyKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjUwcHg7Zm9udC13ZWlnaHQ6MzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPnR1cmU8L3RleHQ+Cjwvc3ZnPg==";function tl(){const e=X(r=>r.theme),t=X(r=>r.branding);return e==="dark"?(t==null?void 0:t.logo_dark)||Zw:(t==null?void 0:t.logo_light)||qw}function hW(){const e=X(g=>g.setSessions),t=X(g=>g.theme),r=X(g=>g.toggleTheme),n=X(g=>g.user),a=X(g=>g.logout),i=X(g=>g.openTabs),s=X(g=>g.activeTabId),o=X(g=>g.branding),c=tl(),u=o!=null&&o.prefix?`${o.prefix} FIXture`:"FIXture",[f,d]=h.useState(!1),[m,p]=h.useState(!1),[v,y]=h.useState(!1);return Lv(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{G.sessions.list().then(e).catch(console.error)},[]),h.useEffect(()=>{if(!n){j_();return}G.customTags.list().then($v).catch(console.error)},[n]),l.jsxs("div",{className:"app",children:[l.jsxs("header",{className:"app-header",children:[l.jsx("img",{src:c,alt:u,className:"app-logo"}),l.jsxs("div",{className:"header-right",children:[n&&l.jsx("span",{className:"header-user",children:n.username}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>d(!0),children:"Scenarios"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p(!0),children:"Custom Tags"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>y(!0),children:"Spec Overlay"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(tn,{to:"/perf",className:"btn btn-sm btn-ghost",title:"Performance testing",children:"Perf"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(tn,{to:"/admin/users",className:"btn-icon",title:"Admin",children:"⚙"}),l.jsx("button",{className:"btn-icon theme-toggle",onClick:r,title:"Toggle theme",children:t==="dark"?"☀":"🌙"}),l.jsx("button",{className:"btn-icon",onClick:a,title:"Sign out",children:"⎋"})]})]}),l.jsxs("div",{className:"app-body",children:[l.jsx(R_,{}),l.jsx("div",{className:"tab-area",children:i.length===0?l.jsx("div",{className:"tab-area-empty",children:"Select a session to open it."}):l.jsxs(l.Fragment,{children:[l.jsx(Yz,{}),l.jsx("div",{className:"tab-panels",children:i.map(g=>l.jsx("div",{className:`tab-panel${g===s?" active":""}`,children:l.jsx(qz,{sessionId:g})},g))})]})})]}),f&&l.jsx(iW,{onClose:()=>d(!1)}),m&&l.jsx(uW,{onClose:()=>p(!1)}),v&&l.jsx(fW,{onClose:()=>y(!1)})]})}function mW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),[c,u]=h.useState(!1),{login:f}=X(),d=tl(),m=fn();h.useEffect(()=>{fetch("/api/auth/register/status").then(v=>v.json()).then(v=>u(v.enabled)).catch(()=>{})},[]);async function p(v){v.preventDefault(),i(""),o(!0);try{const y=await fetch("/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:r})});if(!y.ok){const b=await y.json().catch(()=>({}));throw new Error(b.detail??"Login failed")}const g=await y.json();f(g.access_token,g.user),m("/",{replace:!0})}catch(y){i(y.message)}finally{o(!1)}}return l.jsx("div",{className:"auth-page",children:l.jsxs("div",{className:"auth-card",children:[l.jsx("img",{src:d,alt:"FIXture",className:"auth-logo"}),l.jsx("h2",{children:"Sign in"}),l.jsxs("form",{onSubmit:p,className:"auth-form",children:[l.jsxs("label",{children:["Username",l.jsx("input",{type:"text",value:e,onChange:v=>t(v.target.value),autoComplete:"username",required:!0})]}),l.jsxs("label",{children:["Password",l.jsx("input",{type:"password",value:r,onChange:v=>n(v.target.value),autoComplete:"current-password",required:!0})]}),a&&l.jsx("p",{className:"auth-error",children:a}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Signing in…":"Sign in"})]}),c&&l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["No account? ",l.jsx(tn,{to:"/register",children:"Register"})]})]})})}function pW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),c=tl(),u=fn();async function f(d){d.preventDefault(),i(""),o(!0);try{const m=await fetch("/api/setup/create-admin",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:r})});if(!m.ok){const p=await m.json().catch(()=>({}));throw new Error(p.detail??"Setup failed")}u("/login",{replace:!0})}catch(m){i(m.message)}finally{o(!1)}}return l.jsx("div",{className:"auth-page",children:l.jsxs("div",{className:"auth-card",children:[l.jsx("img",{src:c,alt:"FIXture",className:"auth-logo"}),l.jsx("h2",{children:"Create admin account"}),l.jsx("p",{className:"auth-hint",children:"First-time setup — create the platform admin."}),l.jsxs("form",{onSubmit:f,className:"auth-form",children:[l.jsxs("label",{children:["Username",l.jsx("input",{type:"text",value:e,onChange:d=>t(d.target.value),autoComplete:"username",required:!0})]}),l.jsxs("label",{children:["Password",l.jsx("input",{type:"password",value:r,onChange:d=>n(d.target.value),autoComplete:"new-password",minLength:8,required:!0})]}),a&&l.jsx("p",{className:"auth-error",children:a}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Creating…":"Create admin"})]})]})})}function vW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),[c,u]=h.useState(null),{login:f,theme:d}=X(),m=fn();h.useEffect(()=>{fetch("/api/auth/register/status").then(y=>y.json()).then(u).catch(()=>u({enabled:!1,max_users:0,current_users:0}))},[]);async function p(y){y.preventDefault(),i(""),o(!0);try{const g=await fetch("/api/auth/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:r})});if(!g.ok){const w=await g.json().catch(()=>({}));throw new Error(w.detail??"Registration failed")}const b=await g.json();f(b.access_token,b.user),m("/",{replace:!0})}catch(g){i(g.message)}finally{o(!1)}}const v=c&&c.current_users>=c.max_users;return l.jsx("div",{className:"auth-page",children:l.jsxs("div",{className:"auth-card",children:[l.jsx("img",{src:d==="dark"?Zw:qw,alt:"FIXture",className:"auth-logo"}),l.jsx("h2",{children:"Create account"}),c===null&&l.jsx("p",{className:"auth-hint",children:"Checking registration status…"}),c&&!c.enabled&&l.jsx("p",{className:"auth-error",children:"Registration is currently closed. Contact an administrator."}),c&&c.enabled&&v&&l.jsx("p",{className:"auth-error",children:"Registration limit reached. Contact an administrator."}),c&&c.enabled&&!v&&l.jsxs("form",{onSubmit:p,className:"auth-form",children:[l.jsxs("label",{children:["Username",l.jsx("input",{type:"text",value:e,onChange:y=>t(y.target.value),autoComplete:"username",required:!0})]}),l.jsxs("label",{children:["Password",l.jsx("input",{type:"password",value:r,onChange:y=>n(y.target.value),autoComplete:"new-password",minLength:8,required:!0})]}),a&&l.jsx("p",{className:"auth-error",children:a}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Creating account…":"Create account"})]}),l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["Already have an account? ",l.jsx(tn,{to:"/login",children:"Sign in"})]})]})})}const gW=["accent","bg","bg2","bg3","border","text","text2"];function Kw(e){let t=document.getElementById("branding-style");t||(t=document.createElement("style"),t.id="branding-style",document.head.appendChild(t));const r=gW.filter(n=>e[n]).map(n=>`--${n}: ${e[n]};`).join(" ");if(r?t.textContent=`:root { ${r} } [data-theme="light"] { ${r} }`:t.textContent="",e.favicon){let n=document.querySelector('link[rel="icon"]');n||(n=document.createElement("link"),n.rel="icon",document.head.appendChild(n)),n.href=e.favicon}document.title=e.prefix?`${e.prefix} FIXture`:"FIXture"}function Dl(e){return new Promise((t,r)=>{const n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}const Yw=["user","user_admin","platform_admin"];function yW(e){return new Date(e*1e3).toLocaleString()}function bW({onClose:e,onCreated:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState("user"),[c,u]=h.useState(""),[f,d]=h.useState(!1);async function m(p){p.preventDefault(),u(""),d(!0);try{const v=await G.admin.users.create({username:r,password:a,role:s});t(v),e()}catch(v){u(v.message)}finally{d(!1)}}return l.jsx("div",{className:"modal-overlay",onClick:e,children:l.jsxs("div",{className:"modal",onClick:p=>p.stopPropagation(),children:[l.jsxs("div",{className:"modal-header",children:[l.jsx("span",{children:"Create User"}),l.jsx("button",{className:"btn-icon",onClick:e,children:"✕"})]}),l.jsxs("form",{onSubmit:m,children:[l.jsxs("div",{className:"form-grid",children:[l.jsx("label",{children:"Username"}),l.jsx("input",{value:r,onChange:p=>n(p.target.value),required:!0}),l.jsx("label",{children:"Password"}),l.jsx("input",{type:"password",value:a,onChange:p=>i(p.target.value),minLength:8,required:!0}),l.jsx("label",{children:"Role"}),l.jsx("select",{value:s,onChange:p=>o(p.target.value),children:Yw.map(p=>l.jsx("option",{value:p,children:p},p))})]}),c&&l.jsx("div",{className:"error-msg",children:c}),l.jsxs("div",{className:"modal-footer",children:[l.jsx("button",{type:"button",className:"btn btn-secondary",onClick:e,children:"Cancel"}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:f,children:f?"Creating…":"Create"})]})]})]})})}function xW({user:e,onClose:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),[c,u]=h.useState(!1);async function f(d){d.preventDefault(),i(""),o(!0);try{await G.admin.users.update(e.uid,{password:r}),u(!0)}catch(m){i(m.message)}finally{o(!1)}}return l.jsx("div",{className:"modal-overlay",onClick:t,children:l.jsxs("div",{className:"modal",onClick:d=>d.stopPropagation(),children:[l.jsxs("div",{className:"modal-header",children:[l.jsxs("span",{children:["Reset Password — ",e.username]}),l.jsx("button",{className:"btn-icon",onClick:t,children:"✕"})]}),c?l.jsxs("div",{style:{padding:16},children:[l.jsx("p",{style:{color:"var(--green)"},children:"Password updated successfully."}),l.jsx("div",{className:"modal-footer",children:l.jsx("button",{className:"btn btn-primary",onClick:t,children:"Close"})})]}):l.jsxs("form",{onSubmit:f,children:[l.jsxs("div",{className:"form-grid",children:[l.jsx("label",{children:"New Password"}),l.jsx("input",{type:"password",value:r,onChange:d=>n(d.target.value),minLength:8,required:!0})]}),a&&l.jsx("div",{className:"error-msg",children:a}),l.jsxs("div",{className:"modal-footer",children:[l.jsx("button",{type:"button",className:"btn btn-secondary",onClick:t,children:"Cancel"}),l.jsx("button",{type:"submit",className:"btn btn-primary",disabled:s,children:s?"Saving…":"Save"})]})]})]})})}function wW(){const e=X(I=>I.user),t=fn(),[r,n]=h.useState([]),[a,i]=h.useState(null),[s,o]=h.useState(null),[c,u]=h.useState(null),[f,d]=h.useState(!1),[m,p]=h.useState(null),[v,y]=h.useState(!1),{setBranding:g}=X(),[b,w]=h.useState(!1),[x,j]=h.useState(null),[_,S]=h.useState("");async function O(){try{const[I,R,z,U]=await Promise.all([G.admin.users.list(),G.admin.settings.get(),G.admin.housekeeping.get(),G.admin.branding.get()]);n(I),i(R),o(z),p(U)}catch(I){S(I.message)}}h.useEffect(()=>{O()},[]);async function P(I){try{const R=await G.admin.users.update(I.uid,{is_active:!I.is_active});n(z=>z.map(U=>U.uid===I.uid?R:U))}catch(R){S(R.message)}}async function E(I,R){try{const z=await G.admin.users.update(I.uid,{role:R});n(U=>U.map(J=>J.uid===I.uid?z:J))}catch(z){S(z.message)}}async function k(I){if(confirm(`Delete user "${I.username}"? This cannot be undone.`))try{await G.admin.users.delete(I.uid),n(R=>R.filter(z=>z.uid!==I.uid))}catch(R){S(R.message)}}async function T(){if(a)try{const I=await G.admin.settings.update({registration_enabled:!a.enabled});i(I)}catch(I){S(I.message)}}async function C(I){const R=parseInt(I,10);if(!(isNaN(R)||R<1))try{const z=await G.admin.settings.update({max_users:R});i(z)}catch(z){S(z.message)}}async function W(){if(s)try{const I=await G.admin.housekeeping.update({enabled:!s.enabled});o(I)}catch(I){S(I.message)}}async function L(I,R){const z=parseInt(R,10);if(!(isNaN(z)||z<0))try{const U=await G.admin.housekeeping.update({[I]:z});o(U)}catch(U){S(U.message)}}async function H(){d(!0),u(null);try{const I=await G.admin.housekeeping.runNow();u(`Done — deleted ${I.msgs_deleted} messages, ${I.logs_deleted} log files`)}catch(I){S(I.message)}finally{d(!1)}}return l.jsxs("div",{className:"admin-page",children:[l.jsxs("div",{className:"admin-header",children:[l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[l.jsx("button",{className:"btn-icon",onClick:()=>t("/"),title:"Back",children:"←"}),l.jsx("h1",{children:"User Management"})]}),l.jsx("button",{className:"btn btn-primary btn-sm",onClick:()=>w(!0),children:"+ Create User"})]}),_&&l.jsx("div",{className:"error-msg",style:{margin:"0 0 12px"},children:_}),a&&l.jsxs("div",{className:"admin-settings-bar",children:[l.jsx("span",{className:"admin-settings-label",children:"Self-registration:"}),l.jsx("button",{className:`btn btn-sm ${a.enabled?"btn-stop":"btn-start"}`,onClick:T,children:a.enabled?"Enabled — click to disable":"Disabled — click to enable"}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Max users:"}),l.jsx("input",{type:"number",className:"admin-max-users-input",defaultValue:a.max_users,min:1,onBlur:I=>C(I.target.value)}),l.jsxs("span",{className:"admin-settings-label",style:{color:"var(--text2)"},children:["(",a.current_users," / ",a.max_users," registered)"]})]}),s&&l.jsxs("div",{className:"admin-settings-bar",children:[l.jsx("span",{className:"admin-settings-label",children:"Housekeeping:"}),l.jsx("button",{className:`btn btn-sm ${s.enabled?"btn-stop":"btn-start"}`,onClick:W,children:s.enabled?"Auto — click to disable":"Manual only — click to enable"}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Keep messages (days):"}),l.jsx("input",{type:"number",className:"admin-max-users-input",defaultValue:s.msg_retention_days,min:0,title:"0 = keep forever",onBlur:I=>L("msg_retention_days",I.target.value)}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Keep logs (days):"}),l.jsx("input",{type:"number",className:"admin-max-users-input",defaultValue:s.log_retention_days,min:0,title:"0 = keep forever",onBlur:I=>L("log_retention_days",I.target.value)}),l.jsx("button",{className:"btn btn-sm btn-ghost",style:{marginLeft:16},onClick:H,disabled:f,children:f?"Running…":"Run Now"}),c&&l.jsx("span",{style:{marginLeft:8,color:"var(--green)",fontSize:12},children:c})]}),m&&l.jsxs("div",{className:"admin-settings-bar",style:{flexDirection:"column",alignItems:"flex-start",gap:10},children:[l.jsx("span",{className:"admin-settings-label",style:{fontWeight:700},children:"Branding"}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Brand prefix:"}),l.jsx("input",{className:"admin-max-users-input",style:{width:160},value:m.prefix,placeholder:"e.g. Acme (shown as Acme FIXture)",onChange:I=>p({...m,prefix:I.target.value})}),l.jsx("span",{className:"admin-settings-label",style:{marginLeft:16},children:"Accent colour:"}),l.jsx("input",{type:"color",value:m.accent||"#388bfd",onChange:I=>p({...m,accent:I.target.value}),title:"Primary accent colour"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,accent:""}),title:"Reset to default blue",children:"Reset"})]}),l.jsx("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[["bg","Page background","#0d1117"],["bg2","Panel background","#161b22"],["bg3","Input background","#21262d"],["border","Border colour","#30363d"],["text","Primary text","#e6edf3"],["text2","Secondary text","#8b949e"]].map(([I,R,z])=>l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[l.jsxs("span",{className:"admin-settings-label",children:[R,":"]}),l.jsx("input",{type:"color",value:m[I]||z,onChange:U=>p({...m,[I]:U.target.value}),title:R}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,[I]:""}),title:"Reset to default",children:"↺"})]},I))}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Logo (dark theme):"}),m.logo_dark&&l.jsx("img",{src:m.logo_dark,style:{height:28,background:"#161b22",borderRadius:4,padding:"2px 6px"},alt:"dark logo preview"}),l.jsx("input",{type:"file",accept:"image/*",onChange:async I=>{var z;const R=(z=I.target.files)==null?void 0:z[0];R&&p({...m,logo_dark:await Dl(R)})}}),m.logo_dark&&l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,logo_dark:""}),children:"Clear"})]}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Logo (light theme):"}),m.logo_light&&l.jsx("img",{src:m.logo_light,style:{height:28,background:"#f0f2f5",borderRadius:4,padding:"2px 6px"},alt:"light logo preview"}),l.jsx("input",{type:"file",accept:"image/*",onChange:async I=>{var z;const R=(z=I.target.files)==null?void 0:z[0];R&&p({...m,logo_light:await Dl(R)})}}),m.logo_light&&l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,logo_light:""}),children:"Clear"})]}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Favicon:"}),m.favicon&&l.jsx("img",{src:m.favicon,style:{height:20,width:20},alt:"favicon preview"}),l.jsx("input",{type:"file",accept:"image/png,image/svg+xml,image/x-icon",onChange:async I=>{var z;const R=(z=I.target.files)==null?void 0:z[0];R&&p({...m,favicon:await Dl(R)})}}),m.favicon&&l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,favicon:""}),children:"Clear"})]}),l.jsx("button",{className:"btn btn-sm btn-primary",disabled:v,onClick:async()=>{y(!0);try{const I=await G.admin.branding.update(m);p(I),g(I),Kw(I)}catch(I){S(I.message)}finally{y(!1)}},children:v?"Saving…":"Save Branding"})]}),l.jsxs("table",{className:"admin-table",children:[l.jsx("thead",{children:l.jsxs("tr",{children:[l.jsx("th",{children:"Username"}),l.jsx("th",{children:"Role"}),l.jsx("th",{children:"Status"}),l.jsx("th",{children:"Created"}),l.jsx("th",{children:"Actions"})]})}),l.jsx("tbody",{children:r.map(I=>l.jsxs("tr",{className:I.is_active?"":"admin-row-inactive",children:[l.jsxs("td",{children:[I.username,I.uid===(e==null?void 0:e.uid)&&l.jsx("span",{className:"admin-you-badge",children:" (you)"})]}),l.jsx("td",{children:l.jsx("select",{value:I.role,onChange:R=>E(I,R.target.value),className:"admin-role-select",disabled:I.uid===(e==null?void 0:e.uid),children:Yw.map(R=>l.jsx("option",{value:R,children:R},R))})}),l.jsx("td",{children:l.jsx("span",{style:{color:I.is_active?"var(--green)":"var(--text2)"},children:I.is_active?"Active":"Inactive"})}),l.jsx("td",{style:{color:"var(--text2)",fontSize:11},children:yW(I.created_at)}),l.jsx("td",{children:l.jsxs("div",{className:"admin-row-actions",children:[l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>P(I),disabled:I.uid===(e==null?void 0:e.uid),title:I.is_active?"Deactivate":"Activate",children:I.is_active?"Deactivate":"Activate"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>j(I),title:"Reset password",children:"Reset PW"}),l.jsx("button",{className:"btn btn-sm btn-danger",onClick:()=>k(I),disabled:I.uid===(e==null?void 0:e.uid),title:"Delete user",children:"✕"})]})})]},I.uid))})]}),b&&l.jsx(bW,{onClose:()=>w(!1),onCreated:I=>n(R=>[...R,I])}),x&&l.jsx(xW,{user:x,onClose:()=>j(null)})]})}const Lc={enabled:!1,every_n_fills:1,amend_ratio:1,max_per_order:1,price_offset:.01,pending_replace:!1,amend_wait_ms:2e3},Fc={enabled:!1,after_fills:0,cancel_ratio:1,venue_response:"reject",cxl_rej_reason:0},sv={name:"smoke",mode:"loopback",client_session_id:null,venue_session_id:null,correlation_tag:376,exec_id_tag:25116,rate:{orders_per_window:50,order_window_ms:100,dispatch:"smooth",fill_dispatch:"smooth",fills_per_window:50,fill_window_ms:100,fill_ratio:1,allow_burst:!1,max_burst_multiplier:2},test:{duration:30,max_orders:0,scenario_timeout_ms:3e4,max_pending:1e5,on_saturation:"pause",output:"results",record_latency:!0},amend:Lc,cancel:Fc,payload:{variables:{},order:{symbols:["AAPL","MSFT","GOOGL"],side:"alternate",ord_type:"limit",quantity_min:100,quantity_max:1e3,price_min:99,price_max:101,time_in_force:"0"},fill:{fill_type:"full",send_ack:!0,fills_per_order:1,partial_fill_pct_min:50,partial_fill_pct_max:99,price_variance_ticks:0},order_template_id:null,exec_template_id:null,auto_expiry:!0,expiry_offset:1,expiry_unit:"days",gen_rules:{}}},jW=["loopback","manager","client","venue"];function ov(e,t){return t?Math.round(e/t*1e3):0}function SW({onStarted:e}){var mi,pi;const t=X(N=>N.sessions),[r,n]=h.useState(sv),[a,i]=h.useState(sv.payload.order.symbols.join(", ")),[s,o]=h.useState(!1),[c,u]=h.useState(""),[f,d]=h.useState(!1),[m,p]=h.useState([]),[v,y]=h.useState(""),[g,b]=h.useState([]),w=Object.values(t),x=r.mode==="loopback"||r.mode==="manager"||r.mode==="client",j=r.mode==="loopback"||r.mode==="manager"||r.mode==="venue",_=r.amend??Lc,S=r.cancel??Fc,O=r.client_session_id?t[r.client_session_id]:void 0,P=r.venue_session_id?t[r.venue_session_id]:void 0,E=!x||(O==null?void 0:O.status)==="LOGGED_ON",k=!j||(P==null?void 0:P.status)==="LOGGED_ON",T=(!x||!!r.client_session_id)&&(!j||!!r.venue_session_id),C=(()=>{if(!r.name.trim())return"Name is required";const N=r.rate;if(N.max_burst_multiplier<1)return"Max burst multiplier must be ≥ 1";if(r.test.scenario_timeout_ms<1)return"Order timeout must be > 0";if(r.test.max_pending<1)return"Max pending must be > 0";if(x){if(N.orders_per_window<1||N.order_window_ms<1)return"Order rate must be > 0";if(a.split(",").map(et=>et.trim()).filter(Boolean).length===0)return"At least one symbol is required";const Z=r.payload.order;if(Z.quantity_min<1||Z.quantity_max<1||Z.quantity_min>Z.quantity_max)return"Check quantity min/max";if(Z.price_min<=0||Z.price_max<=0||Z.price_min>Z.price_max)return"Check price min/max";if(_.enabled){if(_.every_n_fills<1)return"Amend every-n-fills must be ≥ 1";if(_.amend_ratio<0||_.amend_ratio>1)return"Amend ratio must be between 0 and 1";if(_.max_per_order<0)return"Max amends per order must be ≥ 0";if(_.price_offset<=0)return"Amend price offset must be > 0";if((_.amend_wait_ms??2e3)<1)return"Amend wait must be > 0"}if(S.enabled){if(S.after_fills<0)return"Cancel after-fills must be ≥ 0";if(S.cancel_ratio<0||S.cancel_ratio>1)return"Cancel ratio must be between 0 and 1";if(S.cxl_rej_reason<0)return"Cancel reject reason must be ≥ 0"}}if(j){if(N.fills_per_window<1||N.fill_window_ms<1)return"Fill rate must be > 0";if(r.payload.fill.fills_per_order<1){if(r.payload.fill.fills_per_order!==0)return"Fills per order must be ≥ 0";if(!S.enabled)return"Fills per order 0 (ack-only) requires Cancels enabled";if(S.after_fills>0)return"Cancel after-fills must be 0 (on ack) when fills per order is 0"}if(N.fill_ratio<0||N.fill_ratio>1)return"Fill ratio must be between 0 and 1"}return null})(),W=T&&E&&k&&!C&&!f;async function L(){try{p(await G.perf.configs.list())}catch{}}h.useEffect(()=>{L()},[]),h.useEffect(()=>{G.templates.list().then(b).catch(()=>{})},[]);const H=g.filter(N=>N.msg_type==="D"),I=g.filter(N=>N.msg_type==="8"),R=!!r.payload.order_template_id,z=!!r.payload.exec_template_id,U=((mi=H.find(N=>N.id===r.payload.order_template_id))==null?void 0:mi.name)??"selected",J=((pi=I.find(N=>N.id===r.payload.exec_template_id))==null?void 0:pi.name)??"selected",ie=N=>n(Z=>({...Z,...N})),ee=N=>n(Z=>({...Z,rate:{...Z.rate,...N}})),ve=N=>n(Z=>({...Z,test:{...Z.test,...N}})),ce=N=>n(Z=>({...Z,amend:{...Lc,...Z.amend,...N}})),Pe=N=>n(Z=>({...Z,cancel:{...Fc,...Z.cancel,...N}})),$=N=>n(Z=>({...Z,payload:{...Z.payload,order:{...Z.payload.order,...N}}})),ae=N=>n(Z=>({...Z,payload:{...Z.payload,fill:{...Z.payload.fill,...N}}})),te=N=>n(Z=>({...Z,payload:{...Z.payload,...N}})),[F,B]=h.useState([]),Q=N=>{B(N),te({gen_rules:Object.fromEntries(N.filter(Z=>Z.tag.trim()).map(Z=>[Z.tag.trim(),Z.value]))})},D=(N,Z)=>Q(F.map((et,Rr)=>Rr===N?{...et,...Z}:et));function Ee(){const N=a.split(",").map(Z=>Z.trim()).filter(Boolean);return{...r,client_session_id:x?r.client_session_id:null,venue_session_id:j?r.venue_session_id:null,payload:{...r.payload,order:{...r.payload.order,symbols:N}}}}async function le(){u(""),d(!0);try{const{run_id:N}=await G.perf.runs.create(Ee());e(N)}catch(N){u(N.message??"Failed to start run")}finally{d(!1)}}async function Re(){u("");try{await G.perf.configs.save(Ee()),await L()}catch(N){u(N.message??"Failed to save config")}}async function $e(N){if(y(N),!!N){u("");try{const Z=await G.perf.configs.get(N);n(Z),i(Z.payload.order.symbols.join(", ")),B(Object.entries(Z.payload.gen_rules??{}).map(([et,Rr])=>({tag:et,value:Rr})))}catch(Z){u(Z.message??"Failed to load config")}}}async function Nt(){if(v&&window.confirm("Delete this saved config?"))try{await G.perf.configs.delete(v),y(""),await L()}catch(N){u(N.message??"Failed to delete config")}}const er=N=>{const Z=t[N];return Z?`${Z.display_name||`${Z.sender_comp_id} → ${Z.target_comp_id}`} (${Z.status})`:N},xn=w.filter(N=>N.session_role!=="venue"),hi=w.filter(N=>N.session_role!=="client");return l.jsxs("div",{className:"perf-form",children:[c&&l.jsx("div",{className:"error-msg",children:c}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Saved configs"}),l.jsxs("div",{className:"perf-field-row",children:[l.jsxs("select",{value:v,onChange:N=>$e(N.target.value),children:[l.jsx("option",{value:"",children:"(load a saved config…)"}),m.map(N=>l.jsxs("option",{value:N.config_id,children:[N.name," — ",N.mode]},N.config_id))]}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:Re,children:"Save current"}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:Nt,disabled:!v,children:"Delete"})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Run"}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Name"}),l.jsx("input",{value:r.name,onChange:N=>ie({name:N.target.value})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Mode"}),l.jsx("select",{value:r.mode,onChange:N=>ie({mode:N.target.value}),children:jW.map(N=>l.jsx("option",{value:N,children:N},N))})]}),x&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Client session"}),l.jsxs("select",{value:r.client_session_id??"",onChange:N=>ie({client_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),xn.map(N=>l.jsx("option",{value:N.session_id,children:er(N.session_id)},N.session_id))]}),O&&l.jsx(lv,{status:O.status})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue session"}),l.jsxs("select",{value:r.venue_session_id??"",onChange:N=>ie({venue_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),hi.map(N=>l.jsx("option",{value:N.session_id,children:er(N.session_id)},N.session_id))]}),P&&l.jsx(lv,{status:P.status})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Rate"}),l.jsxs("div",{className:"perf-field-grid",children:[x&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Orders / window"}),l.jsx("input",{type:"number",min:1,value:r.rate.orders_per_window,onChange:N=>ee({orders_per_window:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order window (ms)"}),l.jsx("input",{type:"number",min:1,value:r.rate.order_window_ms,onChange:N=>ee({order_window_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order dispatch"}),l.jsxs("select",{value:r.rate.dispatch??"smooth",onChange:N=>ee({dispatch:N.target.value}),children:[l.jsx("option",{value:"smooth",children:"smooth (evenly spaced)"}),l.jsx("option",{value:"burst",children:"burst (all at window start)"})]})]})]}),j&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fills / window"}),l.jsx("input",{type:"number",min:1,value:r.rate.fills_per_window,onChange:N=>ee({fills_per_window:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill window (ms)"}),l.jsx("input",{type:"number",min:1,value:r.rate.fill_window_ms,onChange:N=>ee({fill_window_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill ratio"}),l.jsx("input",{type:"number",min:0,max:1,step:.05,value:r.rate.fill_ratio,onChange:N=>ee({fill_ratio:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill dispatch"}),l.jsxs("select",{value:r.rate.fill_dispatch??"smooth",onChange:N=>ee({fill_dispatch:N.target.value}),children:[l.jsx("option",{value:"smooth",children:"smooth (evenly spaced)"}),l.jsx("option",{value:"burst",children:"burst (all at window start)"})]})]})]})]}),l.jsxs("div",{className:"perf-hint",children:[x&&l.jsxs(l.Fragment,{children:["≈ ",ov(r.rate.orders_per_window,r.rate.order_window_ms).toLocaleString()," orders/s"]}),x&&j&&" · ",j&&l.jsxs(l.Fragment,{children:["≈ ",ov(r.rate.fills_per_window,r.rate.fill_window_ms).toLocaleString()," fills/s"]})," ",r.rate.dispatch==="burst"?l.jsxs(l.Fragment,{children:["Burst: ",r.rate.orders_per_window.toLocaleString()," orders fire back-to-back every ",r.rate.order_window_ms,"ms, then silence — sawtooth load."]}):l.jsx(l.Fragment,{children:"Smaller windows smooth the injection."})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Test"}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Duration (s, 0 = no time limit)"}),l.jsx("input",{type:"number",min:0,value:r.test.duration,onChange:N=>ve({duration:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max orders (0 = no count limit)"}),l.jsx("input",{type:"number",min:0,value:r.test.max_orders,onChange:N=>ve({max_orders:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order timeout (ms)"}),l.jsx("input",{type:"number",min:1,step:1e3,value:r.test.scenario_timeout_ms,onChange:N=>ve({scenario_timeout_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"On saturation"}),l.jsxs("select",{value:r.test.on_saturation,onChange:N=>ve({on_saturation:N.target.value}),children:[l.jsx("option",{value:"pause",children:"pause (preserves latency accuracy)"}),l.jsx("option",{value:"drop_oldest",children:"drop_oldest"})]})]})]}),x&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.test.record_latency,onChange:N=>ve({record_latency:N.target.checked})}),"Record per-message latency — one CSV row per message crossing (orders, amends, cancels + acks, fills, replies); enables the per-message latency charts (one-way / round-trip). Downloads as csv.gz after the run."]}),l.jsxs("div",{className:"perf-hint",children:["An order not fully filled within the order timeout is counted ",l.jsx("strong",{children:"Lost"}),". Raise it if rate-limited fills are being flagged lost on full-fill runs."]})]}),x&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Amends (35=G)"}),l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:_.enabled,onChange:N=>ce({enabled:N.target.checked})}),"Amend working orders — price-only OrderCancelReplaceRequest fired every nth fill"]}),_.enabled&&l.jsxs(l.Fragment,{children:[l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Every n fills"}),l.jsx("input",{type:"number",min:1,value:_.every_n_fills,onChange:N=>ce({every_n_fills:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Amend ratio (orders)"}),l.jsx("input",{type:"number",min:0,max:1,step:.05,value:_.amend_ratio,onChange:N=>ce({amend_ratio:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max amends / order (0 = no cap)"}),l.jsx("input",{type:"number",min:0,value:_.max_per_order,onChange:N=>ce({max_per_order:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Price offset (±)"}),l.jsx("input",{type:"number",min:1e-4,step:.01,value:_.price_offset,onChange:N=>ce({price_offset:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Amend wait (ms)"}),l.jsx("input",{type:"number",min:1,step:100,value:_.amend_wait_ms??2e3,onChange:N=>ce({amend_wait_ms:Number(N.target.value)})})]})]}),j&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:_.pending_replace,onChange:N=>ce({pending_replace:N.target.checked})}),"Pending Replace — venue replies 150=E before 150=5 (two ExecReports per amend)"]}),l.jsx("div",{className:"perf-hint",children:"Amends fire only while the order is still working — never on the final fill, so use partial fills or fills per order > 1. The 35=G keeps the order's correlation id (376); amend rate ≈ fills/s ÷ n. Latency is measured amend-2-replaced (150=5). The venue holds an order's remaining fills at each trigger point (up to Amend wait) so the replace is answered while the order is working — with real CumQty — and post-replace fills go out at the amended price."}),j&&r.payload.fill.fill_type==="full"&&r.payload.fill.fills_per_order===1&&l.jsxs("div",{className:"perf-hint",children:["⚠ With a single full fill per order there is never a working order to amend — raise ",l.jsx("strong",{children:"Fills per order"})," or use ",l.jsx("strong",{children:"partial"})," fill type."]})]})]}),x&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Cancels (35=F)"}),l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:S.enabled,onChange:N=>Pe({enabled:N.target.checked})}),"Cancel orders — one OrderCancelRequest per order, on the ack or the nth fill"]}),S.enabled&&l.jsxs(l.Fragment,{children:[l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"After fills (0 = on ack)"}),l.jsx("input",{type:"number",min:0,value:S.after_fills,onChange:N=>Pe({after_fills:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Cancel ratio (orders)"}),l.jsx("input",{type:"number",min:0,max:1,step:.05,value:S.cancel_ratio,onChange:N=>Pe({cancel_ratio:Number(N.target.value)})})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue response"}),l.jsxs("select",{value:S.venue_response,onChange:N=>Pe({venue_response:N.target.value}),children:[l.jsx("option",{value:"reject",children:"Reject — 35=9 OrderCancelReject"}),l.jsx("option",{value:"cancel",children:"Accept — 150=4 Canceled"})]})]}),j&&S.venue_response==="reject"&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"CxlRejReason (102)"}),l.jsx("input",{type:"number",min:0,value:S.cxl_rej_reason,onChange:N=>Pe({cxl_rej_reason:Number(N.target.value)})})]})]}),l.jsxs("div",{className:"perf-hint",children:["The 35=F keeps the order's correlation id (376) and routing/identity tags; latency is measured cancel-2-reply (35=9 or 150=4). For the pure order > ack > cancel > reject sequence set ",l.jsx("strong",{children:"Fills per order"})," to 0 (ack-only orders — the reply is then what completes each order)."]}),j&&S.after_fills===0&&r.payload.fill.fills_per_order>0&&l.jsxs("div",{className:"perf-hint perf-hint-warn",children:[l.jsx("strong",{children:"Heads up:"})," “After fills = 0” only controls when the client sends the 35=F (on the ack) — it does ",l.jsx("strong",{children:"not"})," stop the venue filling. With Fills per order = ",r.payload.fill.fills_per_order," the venue still books each order and the fill-sender races out fills (incl. the full fill 150=2) alongside the cancel. Set ",l.jsx("strong",{children:"Fills per order = 0"})," for a no-fill cancel run."]})]})]}),x&&l.jsxs("div",{className:`perf-form-section ${R?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Order payload",R&&l.jsx("span",{className:"perf-layer-badge",children:"values for template"})]}),R&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Order template ",l.jsx("strong",{children:U})," defines the message shape. These fields supply the values it references via ",l.jsx("code",{children:"{symbol}"})," ",l.jsx("code",{children:"{side}"})," ",l.jsx("code",{children:"{qty}"})," ",l.jsx("code",{children:"{price}"})," tokens. Value generators (",l.jsx("code",{children:"uuid()"}),", ",l.jsx("code",{children:"random_str(n)"}),", ",l.jsx("code",{children:"random_int(a,b)"}),","," ",l.jsx("code",{children:"seq(width)"}),", ",l.jsx("code",{children:"timestamp()"}),", ",l.jsx("code",{children:"date()"}),") are evaluated fresh per order and mix with text as ",l.jsx("code",{children:"ORD-{seq(8)}"}),"; ClOrdID(11) is always made unique per order. ",l.jsx("strong",{children:"Order type"})," is taken from the template."]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Symbols (comma-separated)"}),l.jsx("input",{value:a,onChange:N=>i(N.target.value)})]}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Side"}),l.jsxs("select",{value:r.payload.order.side,onChange:N=>$({side:N.target.value}),children:[l.jsx("option",{value:"alternate",children:"alternate"}),l.jsx("option",{value:"fixed_buy",children:"fixed_buy"}),l.jsx("option",{value:"fixed_sell",children:"fixed_sell"}),l.jsx("option",{value:"random",children:"random"})]})]}),l.jsxs("label",{className:`perf-field ${R?"perf-field-overridden":""}`,children:[l.jsxs("span",{children:["Order type ",R&&l.jsx("em",{className:"perf-field-tag",children:"from template"})]}),l.jsxs("select",{value:r.payload.order.ord_type,disabled:R,onChange:N=>$({ord_type:N.target.value}),children:[l.jsx("option",{value:"limit",children:"limit"}),l.jsx("option",{value:"market",children:"market"})]})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Qty min"}),l.jsx("input",{type:"number",min:1,value:r.payload.order.quantity_min,onChange:N=>$({quantity_min:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Qty max"}),l.jsx("input",{type:"number",min:1,value:r.payload.order.quantity_max,onChange:N=>$({quantity_max:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Price min"}),l.jsx("input",{type:"number",min:0,step:.01,value:r.payload.order.price_min,onChange:N=>$({price_min:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Price max"}),l.jsx("input",{type:"number",min:0,step:.01,value:r.payload.order.price_max,onChange:N=>$({price_max:Number(N.target.value)})})]})]})]}),x&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Send options"}),l.jsxs("div",{className:"perf-field-row",children:[l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.payload.auto_expiry??!0,onChange:N=>te({auto_expiry:N.target.checked})}),"Auto expiry (tags 126 / 432, only if present)"]}),(r.payload.auto_expiry??!0)&&l.jsxs(l.Fragment,{children:[l.jsx("input",{type:"number",min:0,style:{width:64},value:r.payload.expiry_offset??1,onChange:N=>te({expiry_offset:Number(N.target.value)})}),l.jsxs("select",{value:r.payload.expiry_unit??"days",onChange:N=>te({expiry_unit:N.target.value}),children:[l.jsx("option",{value:"minutes",children:"minutes"}),l.jsx("option",{value:"hours",children:"hours"}),l.jsx("option",{value:"days",children:"days"})]}),l.jsx("span",{children:"into the future"})]})]}),F.map((N,Z)=>l.jsxs("div",{className:"perf-field-row",children:[l.jsx("input",{style:{width:72},placeholder:"tag",value:N.tag,onChange:et=>D(Z,{tag:et.target.value.replace(/[^0-9]/g,"")})}),l.jsx("span",{children:"="}),l.jsx("input",{style:{flex:1},placeholder:"uuid() / seq(8) / ORD-{seq(8)} / literal",value:N.value,onChange:et=>D(Z,{value:et.target.value})}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:()=>Q(F.filter((et,Rr)=>Rr!==Z)),children:"✕"})]},Z)),l.jsx("div",{className:"perf-field-row",children:l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Q([...F,{tag:"",value:""}]),children:"+ Add tag rule"})}),l.jsxs("div",{className:"perf-hint",children:["Rules set the tag on every order/scenario message (insert or replace). Generators: ",l.jsx("code",{children:"uuid()"})," · ",l.jsx("code",{children:"random_str(n)"})," · ",l.jsx("code",{children:"random_int(a,b)"})," ·"," ",l.jsx("code",{children:"seq(width[,start])"})," · ",l.jsx("code",{children:"timestamp()"})," · ",l.jsx("code",{children:"date()"})," — mix with text as ",l.jsx("code",{children:"ORD-{seq(8)}"}),". ClOrdID(11) always stays unique per order."]})]}),j&&l.jsxs("div",{className:`perf-form-section ${z?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Fill payload",z&&l.jsx("span",{className:"perf-layer-badge",children:"behavior for template"})]}),z&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Exec template ",l.jsx("strong",{children:J})," reshapes each report (custom tags). These still control fill ",l.jsx("strong",{children:"behavior"})," — how many reports, the fill ratio, and the ack — while the engine sets the standard exec tags."]}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fill type"}),l.jsxs("select",{value:r.payload.fill.fill_type,onChange:N=>ae({fill_type:N.target.value}),children:[l.jsx("option",{value:"full",children:"full"}),l.jsx("option",{value:"partial",children:"partial"}),l.jsx("option",{value:"random",children:"random"})]})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Fills per order (0 = ack-only)"}),l.jsx("input",{type:"number",min:0,value:r.payload.fill.fills_per_order,onChange:N=>ae({fills_per_order:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.payload.fill.send_ack,onChange:N=>ae({send_ack:N.target.checked})}),l.jsx("span",{children:"Send ack before fills (35=8|150=0|39=0)"})]}),r.payload.fill.fill_type!=="full"&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Partial % min"}),l.jsx("input",{type:"number",min:1,max:100,value:r.payload.fill.partial_fill_pct_min,onChange:N=>ae({partial_fill_pct_min:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Partial % max"}),l.jsx("input",{type:"number",min:1,max:100,value:r.payload.fill.partial_fill_pct_max,onChange:N=>ae({partial_fill_pct_max:Number(N.target.value)})})]})]})]})]}),(x||j)&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Templates (optional)"}),l.jsxs("div",{className:"perf-field-grid",children:[x&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order template"}),l.jsxs("select",{value:r.payload.order_template_id??"",onChange:N=>te({order_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),H.map(N=>l.jsx("option",{value:N.id,children:N.name},N.id))]})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Exec report template"}),l.jsxs("select",{value:r.payload.exec_template_id??"",onChange:N=>te({exec_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),I.map(N=>l.jsx("option",{value:N.id,children:N.name},N.id))]})]})]}),l.jsx("div",{className:"perf-hint",children:"Optional FIX templates from your Templates library shape the order / execution report (custom & venue-specific tags). Standard tags (correlation, OrderQty, CumQty, ExecType…) are always set by the engine. Leave as built-in for the default messages."})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsxs("button",{className:"btn btn-sm btn-ghost",onClick:()=>o(N=>!N),children:[s?"▾":"▸"," Advanced"]}),s&&l.jsxs("div",{className:"perf-field-grid",style:{marginTop:8},children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Correlation tag"}),l.jsx("input",{type:"number",min:1,value:r.correlation_tag,onChange:N=>ie({correlation_tag:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Exec ID tag"}),l.jsx("input",{type:"number",min:1,value:r.exec_id_tag,onChange:N=>ie({exec_id_tag:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max pending"}),l.jsx("input",{type:"number",min:1,value:r.test.max_pending,onChange:N=>ve({max_pending:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.rate.allow_burst,onChange:N=>ee({allow_burst:N.target.checked})}),l.jsx("span",{children:"Allow burst"})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max burst multiplier"}),l.jsx("input",{type:"number",min:1,step:.5,value:r.rate.max_burst_multiplier,onChange:N=>ee({max_burst_multiplier:Number(N.target.value)})})]})]})]}),l.jsxs("div",{className:"perf-form-actions",children:[C?l.jsx("span",{className:"perf-not-ready",children:C}):T&&!(E&&k)?l.jsxs("span",{className:"perf-not-ready",children:["Selected session",x&&j?"s":""," must be LOGGED_ON before starting."]}):null,l.jsx("button",{className:"btn btn-primary",onClick:le,disabled:!W,title:T?C||(E&&k?void 0:"Session not logged on yet"):"Select the required session(s)",children:f?"Starting…":"▶ Start run"})]})]})}function lv({status:e}){const t=e==="LOGGED_ON";return l.jsxs("span",{className:`perf-status-chip ${t?"ok":"warn"}`,children:[t?"● ":"○ ",e]})}const cv=600,_W=5e3,OW=new Set(["completed","stopped","error"]);function PW(e){const[t,r]=h.useState(null),[n,a]=h.useState([]),[i,s]=h.useState(!1);return h.useEffect(()=>{if(r(null),a([]),s(!1),!e)return;let o=!1,c=null,u,f=0,d=!1;async function m(){if(o||d||!e)return;let v;try{({ticket:v}=await G.perf.runs.ticket(e))}catch{p();return}if(o)return;const g=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/api/perf/runs/${e}/live?ticket=${v}`;c=new WebSocket(g),c.onopen=()=>{o||(f=0,s(!0))},c.onmessage=b=>{let w;try{w=JSON.parse(b.data)}catch{return}r(w),a(x=>{var S,O,P,E,k,T,C,W,L,H,I,R,z,U,J,ie;const j={t:w.elapsed_s,ops:((S=w.client)==null?void 0:S.ops_live)??0,fps:((O=w.venue)==null?void 0:O.fps_live)??0,aps:((P=w.client)==null?void 0:P.aps_live)??0,cps:((E=w.client)==null?void 0:E.cps_live)??0,crps:((k=w.client)==null?void 0:k.crps_live)??0,ccps:((T=w.client)==null?void 0:T.ccps_live)??0,mco:((C=w.client)==null?void 0:C.msgs_out_live)??0,mci:((W=w.client)==null?void 0:W.msgs_in_live)??0,mvo:((L=w.venue)==null?void 0:L.msgs_out_live)??0,mvi:((H=w.venue)==null?void 0:H.msgs_in_live)??0,tps:w.client?w.client.msgs_out_live+w.client.msgs_in_live:(((I=w.venue)==null?void 0:I.msgs_out_live)??0)+(((R=w.venue)==null?void 0:R.msgs_in_live)??0),p50:(((z=w.response_latency)==null?void 0:z.p50_us)??0)/1e3,p99:(((U=w.response_latency)==null?void 0:U.p99_us)??0)/1e3,fc50:(((J=w.fill_completion_latency)==null?void 0:J.p50_us)??0)/1e3,fc99:(((ie=w.fill_completion_latency)==null?void 0:ie.p99_us)??0)/1e3},_=[...x,j];return _.length>cv?_.slice(-cv):_}),OW.has(w.status)&&(d=!0,c==null||c.close())},c.onclose=()=>{o||(s(!1),d||p())},c.onerror=()=>{c==null||c.close()}}function p(){if(o||d)return;f+=1;const v=Math.min(_W,500*2**Math.min(f,4));u=setTimeout(m,v)}return m(),()=>{o=!0,clearTimeout(u),c==null||c.close()}},[e]),{latest:t,series:n,connected:i}}const Ws="img-export-exclude";async function NW(e,t,r){const{toPng:n,toJpeg:a}=await Uv(async()=>{const{toPng:u,toJpeg:f}=await import("./index-CyNOPa0n.js");return{toPng:u,toJpeg:f}},[]),i=getComputedStyle(document.body).backgroundColor||"#111",o=await(r==="png"?n:a)(e,{backgroundColor:i,pixelRatio:2,quality:.95,filter:u=>!(u instanceof HTMLElement&&u.classList.contains(Ws))}),c=document.createElement("a");c.href=o,c.download=t,c.click()}function CW(e,t){const r=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");return`${e}-${r}.${t}`}const Rl=new Set(["completed","stopped","error"]);function Wt(e){return e?(e/1e3).toFixed(3):"—"}function Xw(e,t){return t<=0?"—":(e/t).toFixed(2)}function Di({title:e,sub:t,stats:r}){return l.jsxs("div",{className:"perf-lat-card",children:[l.jsxs("div",{className:"perf-lat-title",children:[e,t&&l.jsx("span",{className:"perf-lat-sub",children:t})]}),l.jsx("div",{className:"perf-lat-grid",children:["p50_us","p95_us","p99_us","max_us"].map(n=>l.jsxs("div",{className:"perf-lat-cell",children:[l.jsx("span",{className:"perf-lat-val",children:Wt(r[n])}),l.jsxs("span",{className:"perf-lat-lbl",children:[n.replace("_us","")," ms"]})]},n))})]})}function EW({runId:e}){var _,S,O;const{latest:t,series:r,connected:n}=PW(e),[a,i]=h.useState(null),[s,o]=h.useState(!1),[c,u]=h.useState("direction"),f=h.useRef(null);h.useEffect(()=>{let P=!0;i(null);let E;const k=async()=>{try{const T=await G.perf.runs.get(e);if(!P)return;i(T),Rl.has(T.status)&&clearInterval(E)}catch{}};return E=setInterval(k,2e3),k(),()=>{P=!1,clearInterval(E)}},[e]);const d=t?Rl.has(t.status):a?Rl.has(a.status):!1,m=!d;async function p(){try{await G.perf.runs.stop(e)}catch{}}async function v(P){if(!(!f.current||s)){o(!0);try{const E=P==="png"?"png":"jpg";await NW(f.current,CW(`perf-${e.slice(0,8)}`,E),P)}catch(E){console.error("image export failed",E)}finally{o(!1)}}}const y=t==null?void 0:t.client,g=t==null?void 0:t.venue,b=t==null?void 0:t.errors,w=(b==null?void 0:b.saturated)||(t==null?void 0:t.status)==="saturated",x=t==null?void 0:t.rate_config,j=[];return y&&(j.push({label:"orders/s",value:Math.round(y.ops_live).toLocaleString()}),(x==null?void 0:x.orders_per_window)!=null&&(x==null?void 0:x.order_window_ms)!=null&&j.push({label:"order rate (cfg)",value:`${x.orders_per_window.toLocaleString()} / ${x.order_window_ms}ms`}),j.push({label:"orders sent",value:y.orders_sent.toLocaleString()},{label:"pending",value:y.pending.toLocaleString()},{label:"dropped",value:y.dropped.toLocaleString()}),(y.amends_sent>0||y.aps_live>0)&&(j.push({label:"amends/s",value:Math.round(y.aps_live).toLocaleString()},{label:"amends",value:y.amends_sent.toLocaleString()},{label:"replaces",value:y.replaces_received.toLocaleString()}),y.amends_lost>0&&j.push({label:"amends lost",value:y.amends_lost.toLocaleString()})),y.amend_rejects>0&&j.push({label:"amend rejects (35=9)",value:y.amend_rejects.toLocaleString()}),(y.cancels_sent??0)>0&&(j.push({label:"cancels (35=F)",value:y.cancels_sent.toLocaleString()}),(y.cancel_rejects??0)>0&&j.push({label:"cancel rejects (35=9)",value:y.cancel_rejects.toLocaleString()}),(y.cancels_canceled??0)>0&&j.push({label:"canceled (150=4)",value:y.cancels_canceled.toLocaleString()}),(y.cancels_lost??0)>0&&j.push({label:"cancels lost",value:y.cancels_lost.toLocaleString()}))),g&&(j.push({label:"fills/s",value:Math.round(g.fps_live).toLocaleString()}),(x==null?void 0:x.fills_per_window)!=null&&(x==null?void 0:x.fill_window_ms)!=null&&j.push({label:"fill rate (cfg)",value:`${x.fills_per_window.toLocaleString()} / ${x.fill_window_ms}ms`}),j.push({label:"fills",value:g.fills_sent.toLocaleString()},{label:"fills/order",value:Xw(g.fills_sent,g.orders_received-g.unfilled)},{label:"fill ratio",value:g.fill_ratio.toFixed(3)},{label:"recv",value:g.orders_received.toLocaleString()},{label:"acks",value:g.acks_sent.toLocaleString()},{label:"unfilled",value:g.unfilled.toLocaleString()}),g.amends_received>0&&j.push({label:"amends recv",value:g.amends_received.toLocaleString()}),g.amends_uncorrelated>0&&j.push({label:"amends no-corr",value:g.amends_uncorrelated.toLocaleString()}),g.amend_waits_expired>0&&j.push({label:"amend waits expired",value:g.amend_waits_expired.toLocaleString()}),(g.cancels_received??0)>0&&j.push({label:"cancels recv",value:g.cancels_received.toLocaleString()}),(g.cancels_uncorrelated??0)>0&&j.push({label:"cancels no-corr",value:g.cancels_uncorrelated.toLocaleString()})),b&&j.push({label:"lost",value:b.lost_timeout.toLocaleString()},{label:"rejected",value:b.rejected.toLocaleString()}),l.jsxs("div",{className:"perf-dashboard",ref:f,children:[l.jsxs("div",{className:"perf-dash-header",children:[l.jsxs("div",{className:"perf-dash-status",children:[l.jsx("span",{className:`badge perf-state-${(t==null?void 0:t.status)??(a==null?void 0:a.status)??"pending"}`,children:(t==null?void 0:t.status)??(a==null?void 0:a.status)??"pending"}),t&&l.jsxs("span",{className:"perf-dash-elapsed",children:[Math.round(t.elapsed_s),"s",t.duration_s?` / ${t.duration_s}s`:""," · ",t.mode]}),l.jsx("span",{className:`perf-conn ${n?"on":"off"}`,children:n?"● live":"○ offline"})]}),l.jsxs("div",{className:`perf-dash-actions ${Ws}`,children:[m&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:p,children:"■ Stop"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>G.perf.runs.export(e,"messages"),children:"Export CSV"}),d&&l.jsx("button",{className:"btn btn-sm btn-ghost",title:"Per-message latency rows (requires record per-message latency)",onClick:()=>G.perf.runs.export(e,"latency"),children:"Latency CSV"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>G.perf.runs.export(e,"both"),children:"Export ZIP"}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:s,onClick:()=>v("png"),children:s?"…":"PNG"}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:s,onClick:()=>v("jpeg"),children:s?"…":"JPG"})]})]}),w&&l.jsxs("div",{className:"perf-saturated-banner",children:["⚠ SATURATED — injection paused to protect latency measurement.",y&&l.jsxs(l.Fragment,{children:[" Pending ",y.pending.toLocaleString(),", dropped ",y.dropped.toLocaleString(),"."]})]}),(_=a==null?void 0:a.warnings)==null?void 0:_.map((P,E)=>l.jsxs("div",{className:"perf-warning-banner",children:["⚠ ",P]},E)),!t&&!d&&l.jsx("div",{className:"loading-msg",children:"Waiting for first snapshot…"}),j.length>0&&l.jsx("div",{className:"stat-cards",children:j.map(({label:P,value:E})=>l.jsxs("div",{className:"stat-card",children:[l.jsx("span",{className:"stat-card-value",children:E}),l.jsx("span",{className:"stat-card-label",children:P})]},P))}),t&&l.jsxs("div",{className:"perf-lat-row",children:[l.jsx(Di,{title:"Response latency",sub:"order-2-ack",stats:t.response_latency}),l.jsx(Di,{title:"Fill-completion latency",sub:"order-2-last-fill",stats:t.fill_completion_latency}),((S=t.amend_latency)==null?void 0:S.count)>0&&l.jsx(Di,{title:"Amend latency",sub:"amend-2-replaced",stats:t.amend_latency}),((O=t.cancel_latency)==null?void 0:O.count)>0&&l.jsx(Di,{title:"Cancel latency",sub:"cancel-2-reply",stats:t.cancel_latency})]}),r.length>1&&l.jsxs(l.Fragment,{children:[l.jsx("div",{className:"analysis-section-title",children:"Throughput"}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),l.jsx(Ie,{type:"monotone",dataKey:"ops",name:"orders/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"fps",name:"fills/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.amends_sent)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"aps",name:"amends/s",stroke:"#c084fc",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancels_sent)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"cps",name:"cancels/s (35=F)",stroke:"#fb923c",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancel_rejects)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"crps",name:"cancel-rejects/s (35=9)",stroke:"#f43f5e",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancels_canceled)??0)>0&&l.jsx(Ie,{type:"monotone",dataKey:"ccps",name:"canceled/s (150=4)",stroke:"#2dd4bf",dot:!1,isAnimationActive:!1})]})})}),l.jsxs("div",{className:"analysis-section-title perf-mps-title",children:[l.jsx("span",{children:"Messages/sec"}),l.jsxs("select",{className:Ws,value:c,onChange:P=>u(P.target.value),children:[l.jsx("option",{value:"direction",children:"by direction"}),l.jsx("option",{value:"leg",children:"per leg"})]})]}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),c==="direction"?l.jsxs(l.Fragment,{children:[l.jsx(Ie,{type:"monotone",dataKey:"tps",name:"total msgs/s",stroke:"#fbbf24",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"mco",name:"sent/s (requests)",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"mci",name:"received/s (replies)",stroke:"#a78bfa",dot:!1,isAnimationActive:!1})]}):l.jsxs(l.Fragment,{children:[l.jsx(Ie,{type:"monotone",dataKey:P=>P.mco+P.mci,name:"client session/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:P=>P.mvo+P.mvi,name:"venue session/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1})]})]})})}),l.jsx("div",{className:"analysis-section-title",children:"Response latency (ms)"}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),l.jsx(Ie,{type:"monotone",dataKey:"p50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"p99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})}),l.jsx("div",{className:"analysis-section-title",children:"Fill-completion latency (ms)"}),l.jsx("div",{className:"analysis-chart-wrap",children:l.jsx(Gr,{width:"100%",height:200,children:l.jsxs(ja,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Or,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Pr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:P=>`${Math.round(P)}s`}),l.jsx(Nr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Yr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:P=>`${Math.round(Number(P))}s`}),l.jsx(kn,{wrapperStyle:{fontSize:11}}),l.jsx(Ie,{type:"monotone",dataKey:"fc50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Ie,{type:"monotone",dataKey:"fc99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})})]}),d&&l.jsx(AW,{runId:e}),d&&(a==null?void 0:a.summary)&&l.jsx(kW,{status:a})]})}function AW({runId:e}){const[t,r]=h.useState("one_way"),[n,a]=h.useState("both"),[i,s]=h.useState("log"),[o,c]=h.useState(null),[u,f]=h.useState(null),[d,m]=h.useState(!0);if(h.useEffect(()=>{let v=!0,y=null;return m(!0),f(null),G.perf.runs.chart(e,{metric:t,agg:n,scale:i}).then(g=>{if(!v){URL.revokeObjectURL(g);return}y=g,c(g)}).catch(g=>{v&&f(g instanceof Error?g.message:String(g))}).finally(()=>{v&&m(!1)}),()=>{v=!1,y&&URL.revokeObjectURL(y)}},[e,t,n,i]),u&&/record_latency|No per-message|not found/i.test(u))return null;function p(){if(!o)return;const v=document.createElement("a");v.href=o,v.download=`${e}_latency.png`,v.click()}return l.jsxs("div",{className:`perf-chart-section ${Ws}`,children:[l.jsx("div",{className:"analysis-section-title",children:"Per-message latency"}),l.jsxs("div",{className:"perf-chart-controls",children:[l.jsxs("label",{children:["Latency ",l.jsxs("select",{value:t,onChange:v=>r(v.target.value),children:[l.jsx("option",{value:"one_way",children:"per-message (one-way)"}),l.jsx("option",{value:"round_trip",children:"round-trip"}),l.jsx("option",{value:"both",children:"both"})]})]}),l.jsxs("label",{children:["View ",l.jsxs("select",{value:n,onChange:v=>a(v.target.value),children:[l.jsx("option",{value:"both",children:"scatter + p50/p99/max"}),l.jsx("option",{value:"scatter",children:"scatter"}),l.jsx("option",{value:"percentile",children:"p50/p99/max"})]})]}),l.jsxs("label",{children:["Scale ",l.jsxs("select",{value:i,onChange:v=>s(v.target.value),children:[l.jsx("option",{value:"log",children:"log"}),l.jsx("option",{value:"linear",children:"linear"})]})]}),l.jsx("button",{className:"btn btn-sm",onClick:p,disabled:!o,children:"Download PNG"})]}),d&&l.jsx("div",{className:"perf-hint",children:"Rendering chart…"}),u&&!d&&l.jsx("div",{className:"error-msg",children:u}),o&&!d&&l.jsx("img",{src:o,alt:"Per-message latency by kind",className:"perf-chart-img"})]})}function kW({status:e}){const t=e.summary,r=[["Orders sent",t.orders_sent.toLocaleString()],["Acks sent",t.acks_sent.toLocaleString()],["Fills sent",t.fills_sent.toLocaleString()],["Responses",t.responses.toLocaleString()],["Completions (fully filled)",t.completions.toLocaleString()],["Fills per order (completed)",Xw(t.fills_sent,t.completions)],["Fill ratio (completed)",t.fill_ratio.toFixed(3)],["Lost / dropped / rejected",`${t.lost_timeout} / ${t.dropped} / ${t.rejected}`],["Response p50 / p99 (ms)",`${Wt(t.response_latency.p50_us)} / ${Wt(t.response_latency.p99_us)}`],["Response max (ms)",Wt(t.response_latency.max_us)],["Fill-completion p50 / p99 (ms)",`${Wt(t.fill_completion_latency.p50_us)} / ${Wt(t.fill_completion_latency.p99_us)}`]];return t.amends_sent&&(r.push(["Amends sent / replaced / rejected / lost",`${t.amends_sent} / ${t.replaces_received??0} / ${t.amend_rejects??0} / ${t.amends_lost??0}`],["Amends recv (venue) / no-corr / waits expired",`${t.amends_received??0} / ${t.amends_uncorrelated??0} / ${t.amend_waits_expired??0}`]),t.amend_latency&&r.push(["Amend p50 / p99 (ms)",`${Wt(t.amend_latency.p50_us)} / ${Wt(t.amend_latency.p99_us)}`])),t.cancels_sent&&(r.push(["Cancels sent / rejected (35=9) / canceled (150=4) / lost",`${t.cancels_sent} / ${t.cancel_rejects??0} / ${t.cancels_canceled??0} / ${t.cancels_lost??0}`],["Cancels recv (venue) / no-corr",`${t.cancels_received??0} / ${t.cancels_uncorrelated??0}`]),t.cancel_latency&&r.push(["Cancel p50 / p99 (ms)",`${Wt(t.cancel_latency.p50_us)} / ${Wt(t.cancel_latency.p99_us)}`])),l.jsxs("div",{className:"perf-summary-card",children:[l.jsxs("div",{className:"perf-section-title",children:["Run summary — ",e.status]}),l.jsx("table",{className:"perf-summary-table",children:l.jsx("tbody",{children:r.map(([n,a])=>l.jsxs("tr",{children:[l.jsx("td",{children:n}),l.jsx("td",{className:"mono",children:a})]},n))})})]})}const IW=new Set(["completed","stopped","error"]);function TW(e){if(!e)return"—";try{return new Date(e*1e3).toLocaleTimeString()}catch{return"—"}}function MW({activeRunId:e,onSelect:t,refreshKey:r}){const[n,a]=h.useState([]);h.useEffect(()=>{let s=!0;const o=()=>G.perf.runs.list().then(u=>{s&&a(u)}).catch(()=>{});o();const c=setInterval(o,2e3);return()=>{s=!1,clearInterval(c)}},[r]);async function i(s,o){s.stopPropagation();try{await G.perf.runs.stop(o)}catch{}}return n.length===0?l.jsx("div",{className:"perf-runlist-empty",children:"No runs yet. Configure and start one."}):l.jsxs("table",{className:"perf-runlist",children:[l.jsx("thead",{children:l.jsxs("tr",{children:[l.jsx("th",{children:"Name"}),l.jsx("th",{children:"Mode"}),l.jsx("th",{children:"Status"}),l.jsx("th",{children:"Started"}),l.jsx("th",{children:"Elapsed"}),l.jsx("th",{children:"Orders"}),l.jsx("th",{children:"Fill"}),l.jsx("th",{})]})}),l.jsx("tbody",{children:n.map(s=>l.jsxs("tr",{className:s.run_id===e?"perf-run-active":"",onClick:()=>t(s.run_id),children:[l.jsx("td",{children:s.name}),l.jsx("td",{children:s.mode}),l.jsx("td",{children:l.jsx("span",{className:`badge perf-state-${s.status}`,children:s.status})}),l.jsx("td",{children:TW(s.started_at)}),l.jsxs("td",{className:"mono",children:[Math.round(s.elapsed_s),"s"]}),l.jsx("td",{className:"mono",children:s.summary?s.summary.orders_sent.toLocaleString():"—"}),l.jsx("td",{className:"mono",children:s.summary?s.summary.fill_ratio.toFixed(2):"—"}),l.jsx("td",{className:"perf-runlist-actions",children:!IW.has(s.status)&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:o=>i(o,s.run_id),children:"Stop"})})]},s.run_id))})]})}const $l=200,Tn=(e,t,r=120)=>({field:e,headerName:t,width:r,type:"numericColumn",valueFormatter:n=>n.value==null?"":Number(n.value).toLocaleString()}),Bc=(e,t)=>({field:e,headerName:t,width:150,type:"numericColumn",valueFormatter:r=>r.value==null?"":`${Math.round(Number(r.value)).toLocaleString()}`}),DW=[{field:"clordid",headerName:"ClOrdID",width:150},{field:"corr_id",headerName:"Corr ID",width:150},{field:"msg_type",headerName:"Type",width:80},{field:"symbol",headerName:"Symbol",width:90},{field:"side",headerName:"Side",width:70},Tn("qty","Qty",90),Tn("price","Price",100),Bc("response_latency_us","Resp µs"),Bc("fill_latency_us","Fill µs"),Tn("fill_qty","Fill qty",100),Tn("fill_price","Fill price",110),{field:"status",headerName:"Status",width:110,cellStyle:e=>e.value==="lost"||e.value==="dropped"?{color:"#facc15",fontWeight:"bold"}:void 0}],RW=[{field:"scenario_name",headerName:"Scenario",flex:1,minWidth:160},Bc("latency_us","Latency µs"),Tn("msg_count","Msgs",90),Tn("fill_ratio","Fill ratio",110),{field:"status",headerName:"Status",width:110,cellStyle:e=>e.value==="lost"||e.value==="dropped"?{color:"#facc15",fontWeight:"bold"}:void 0}];function $W({runId:e}){const t=X(g=>g.theme),[r,n]=h.useState("messages"),[a,i]=h.useState(0),[s,o]=h.useState([]),[c,u]=h.useState(0),[f,d]=h.useState(!1);h.useEffect(()=>{i(0)},[r,e]),h.useEffect(()=>{let g=!0;return d(!0),(r==="messages"?G.perf.runs.messages:G.perf.runs.scenarios)(e,{limit:$l,offset:a*$l}).then(w=>{g&&(o(w.items),u(w.total))}).catch(()=>{g&&(o([]),u(0))}).finally(()=>{g&&d(!1)}),()=>{g=!1}},[e,r,a]);const m=h.useMemo(()=>r==="messages"?DW:RW,[r]),p=h.useMemo(()=>({sortable:!0,filter:!0,resizable:!0}),[]),v=Math.max(0,Math.ceil(c/$l)-1),y=t==="dark"?"ag-theme-quartz-dark":"ag-theme-quartz";return l.jsxs("div",{className:"perf-results",children:[l.jsxs("div",{className:"perf-results-toolbar",children:[l.jsxs("div",{className:"perf-results-tabs",children:[l.jsx("button",{className:`btn btn-sm ${r==="messages"?"btn-primary":"btn-ghost"}`,onClick:()=>n("messages"),children:"Messages"}),l.jsx("button",{className:`btn btn-sm ${r==="scenarios"?"btn-primary":"btn-ghost"}`,onClick:()=>n("scenarios"),children:"Scenarios"})]}),l.jsxs("div",{className:"perf-results-pager",children:[l.jsxs("span",{children:[c.toLocaleString()," rows"]}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:a<=0||f,onClick:()=>i(g=>Math.max(0,g-1)),children:"‹ Prev"}),l.jsxs("span",{children:["Page ",a+1," / ",v+1]}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:a>=v||f,onClick:()=>i(g=>Math.min(v,g+1)),children:"Next ›"})]})]}),l.jsx("div",{className:`${y} perf-results-grid`,children:l.jsx(zc,{rowData:s,columnDefs:m,defaultColDef:p,animateRows:!1,suppressCellFocus:!0})})]})}function LW(){const{theme:e,toggleTheme:t,branding:r,setSessions:n}=X(),a=tl(),i=r!=null&&r.prefix?`${r.prefix} FIXture`:"FIXture";Lv(),h.useEffect(()=>{G.sessions.list().then(n).catch(()=>{})},[]);const[s,o]=h.useState("configure"),[c,u]=h.useState(null),[f,d]=h.useState(0);function m(y){u(y),d(g=>g+1),o("live")}function p(y){u(y),o("live")}const v=[{id:"configure",label:"Configure"},{id:"live",label:"Live",disabled:!c},{id:"results",label:"Results",disabled:!c},{id:"history",label:"History"}];return l.jsxs("div",{className:"app perf-page",children:[l.jsxs("header",{className:"app-header",children:[l.jsx("img",{src:a,alt:i,className:"app-logo"}),l.jsx("div",{className:"perf-page-title",children:"Performance Testing"}),l.jsxs("div",{className:"header-right",children:[l.jsx(tn,{to:"/",className:"btn btn-sm btn-ghost",children:"← Sessions"}),l.jsx("button",{className:"btn-icon theme-toggle",onClick:t,title:"Toggle theme",children:e==="dark"?"☀":"🌙"})]})]}),l.jsx("div",{className:"perf-tabbar",children:v.map(y=>l.jsx("button",{className:`perf-tab${s===y.id?" active":""}`,disabled:y.disabled,onClick:()=>o(y.id),children:y.label},y.id))}),l.jsxs("div",{className:"perf-body",children:[s==="configure"&&l.jsx("div",{className:"perf-pane perf-pane-form",children:l.jsx(SW,{onStarted:m})}),s==="live"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(EW,{runId:c},c)}),s==="results"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx($W,{runId:c},c)}),s==="history"&&l.jsx("div",{className:"perf-pane",children:l.jsx(MW,{activeRunId:c,onSelect:p,refreshKey:f})})]})]})}function FW(){return X(t=>t.token)?l.jsx(Pv,{}):l.jsx(Gi,{to:"/login",replace:!0})}function BW(){const e=X(t=>t.user);return e?e.role!=="platform_admin"?l.jsx(Gi,{to:"/",replace:!0}):l.jsx(Pv,{}):l.jsx(Gi,{to:"/login",replace:!0})}function zW(){const e=X(t=>t.setBranding);h.useEffect(()=>{fetch("/api/branding").then(t=>t.json()).then(t=>{e(t),Kw(t)}).catch(()=>{})},[])}function WW(){const e=fn(),t=X(r=>r.theme);return zW(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{fetch("/api/setup/status").then(r=>r.json()).then(r=>{r.setup_required&&e("/setup",{replace:!0})}).catch(()=>{})},[]),l.jsxs(uS,{children:[l.jsx(zt,{path:"/setup",element:l.jsx(pW,{})}),l.jsx(zt,{path:"/login",element:l.jsx(mW,{})}),l.jsx(zt,{path:"/register",element:l.jsx(vW,{})}),l.jsxs(zt,{element:l.jsx(FW,{}),children:[l.jsx(zt,{path:"/",element:l.jsx(hW,{})}),l.jsxs(zt,{element:l.jsx(BW,{}),children:[l.jsx(zt,{path:"/admin/users",element:l.jsx(wW,{})}),l.jsx(zt,{path:"/perf",element:l.jsx(LW,{})})]})]}),l.jsx(zt,{path:"*",element:l.jsx(Gi,{to:"/",replace:!0})})]})}Ll.createRoot(document.getElementById("root")).render(l.jsx(uv.StrictMode,{children:l.jsx(MS,{children:l.jsx(WW,{})})}));
|