fixtureqa 0.4.28__tar.gz → 0.4.29__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.28/fixtureqa.egg-info → fixtureqa-0.4.29}/PKG-INFO +1 -1
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_engine.py +7 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_models.py +1 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_stats.py +3 -1
- fixtureqa-0.4.28/fixture/static/assets/index-COvq8e1-.js → fixtureqa-0.4.29/fixture/static/assets/index-C2JvCG6w.js +1 -1
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/static/index.html +1 -1
- {fixtureqa-0.4.28 → fixtureqa-0.4.29/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixtureqa.egg-info/SOURCES.txt +1 -1
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/pyproject.toml +1 -1
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_perf_engine.py +19 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/LICENSE +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/README.md +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/__init__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/__main__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/app.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/deps.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/auth.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/events.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/latency_csv_writer.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/models.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_charts.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_payload.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/session.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/server.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/static/assets/index-C9bcm_lN.css +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/setup.cfg +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_auth.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_config_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_health.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_inbound.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_message_store.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_session_auto_response.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_sessions.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_templates.py +0 -0
- {fixtureqa-0.4.28 → fixtureqa-0.4.29}/tests/test_ws.py +0 -0
|
@@ -331,6 +331,12 @@ class PerfRun:
|
|
|
331
331
|
self.stats.amends_lost += 1
|
|
332
332
|
if entry.get("cancel_inflight"):
|
|
333
333
|
self.stats.cancels_lost += 1
|
|
334
|
+
if not entry.get("completed"):
|
|
335
|
+
# In-flight when the run ended — truncated, not timeout-reaped. Counted
|
|
336
|
+
# separately from lost_timeout so the summary reconciles (orders_sent =
|
|
337
|
+
# completions + lost_timeout + lost_shutdown) and "stuck" stays distinct
|
|
338
|
+
# from "ran out of time".
|
|
339
|
+
self.stats.lost_shutdown += 1
|
|
334
340
|
self._finalize(entry, "filled" if entry.get("completed") else "lost")
|
|
335
341
|
self._pending.clear()
|
|
336
342
|
self._venue_orders.clear()
|
|
@@ -1376,6 +1382,7 @@ class PerfRun:
|
|
|
1376
1382
|
cancel_latency=LatencyStats(**self.stats.cancel_latency.stats()),
|
|
1377
1383
|
scenario_latency=LatencyStats(**self.stats.scenario_latency.stats()),
|
|
1378
1384
|
errors=SnapshotErrors(lost_timeout=self.stats.lost_timeout,
|
|
1385
|
+
lost_shutdown=self.stats.lost_shutdown,
|
|
1379
1386
|
rejected=self.stats.rejected,
|
|
1380
1387
|
saturated=self.status == "saturated"))
|
|
1381
1388
|
self.latest = snap.model_dump()
|
|
@@ -163,7 +163,8 @@ class PerfStats:
|
|
|
163
163
|
# middle_manager relay: messages forwarded each direction
|
|
164
164
|
self.relay_fwd_down = 0 # client→venue direction (orders/amends/cancels)
|
|
165
165
|
self.relay_fwd_up = 0 # venue→client direction (execs/rejects)
|
|
166
|
-
self.lost_timeout = 0
|
|
166
|
+
self.lost_timeout = 0 # reaped mid-run by the timeout sweeper (genuinely stuck)
|
|
167
|
+
self.lost_shutdown = 0 # still in-flight when the run ended (truncated, not stuck)
|
|
167
168
|
self.dropped = 0
|
|
168
169
|
self.rejected = 0
|
|
169
170
|
self.pending = 0 # gauge, set by the engine
|
|
@@ -263,6 +264,7 @@ class PerfStats:
|
|
|
263
264
|
"cancels_received": self.cancels_received,
|
|
264
265
|
"cancels_uncorrelated": self.cancels_uncorrelated,
|
|
265
266
|
"lost_timeout": self.lost_timeout,
|
|
267
|
+
"lost_shutdown": self.lost_shutdown,
|
|
266
268
|
"dropped": self.dropped,
|
|
267
269
|
"rejected": self.rejected,
|
|
268
270
|
"scenarios_dispatched": self.scenarios_dispatched,
|
|
@@ -99,4 +99,4 @@ Please change the parent <Route path="${b}"> to <Route path="${b==="/"?"*":`${b}
|
|
|
99
99
|
]
|
|
100
100
|
}
|
|
101
101
|
]
|
|
102
|
-
}`;function hW({onClose:e}){const[t,r]=h.useState(""),[n,a]=h.useState(""),[i,s]=h.useState(!1);h.useEffect(()=>{Z.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 Z.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:fW})}),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 Vw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgCgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGNpcmNsZSBjeD0iMjA3IiBjeT0iNzgiIHI9IjEwIiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjY1IiB4Mj0iMjQ4IiB5Mj0iNjUiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDAsIDE4MCwgMjAwKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMCwgMTgwLCAyMDApO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNztmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI5MSIgeDI9IjI0NCIgeTI9IjkxIiBzdHJva2U9IiMwMGI0YzgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjQ1IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigwLCAxODAsIDIwMCk7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC40NTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgoKICA8dGV4dCB4PSIyNzIiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtbW9ubykiIGZvbnQtc2l6ZT0iNjYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6NTBweDtmb250LXdlaWdodDozMDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byI+dHVyZTwvdGV4dD4KPC9zdmc+",Jw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSIxMCIgZmlsbD0iIzFhM2E1YyIgc3R5bGU9ImZpbGw6cmdiKDI2LCA1OCwgOTIpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI2NSIgeDI9IjI0OCIgeTI9IjY1IiBzdHJva2U9IiMxYTNhNWMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigyNiwgNTgsIDkyKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMjYsIDU4LCA5Mik7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC43O2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjkxIiB4Mj0iMjQ0IiB5Mj0iOTEiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNDUiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDI2LCA1OCwgOTIpO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNDU7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJnF1b3Q7U2Vnb2UgVUkmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7Zm9udC13ZWlnaHQ6NDAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iLz4KCiAgPHRleHQgeD0iMjcyIiB5PSIxMDUiIGZvbnQtZmFtaWx5PSJ2YXIoLS1mb250LW1vbm8pIiBmb250LXNpemU9IjY2IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMWEzYTVjIiBsZXR0ZXItc3BhY2luZz0iLTEiIHN0eWxlPSJmaWxsOnJnYigyNiwgNTgsIDkyKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjUwcHg7Zm9udC13ZWlnaHQ6MzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPnR1cmU8L3RleHQ+Cjwvc3ZnPg==";function al(){const e=X(r=>r.theme),t=X(r=>r.branding);return e==="dark"?(t==null?void 0:t.logo_dark)||Vw:(t==null?void 0:t.logo_light)||Jw}function mW(){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=al(),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 Uv(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{Z.sessions.list().then(e).catch(console.error)},[]),h.useEffect(()=>{if(!n){_S();return}Z.customTags.list().then(Wv).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(an,{to:"/perf",className:"btn btn-sm btn-ghost",title:"Performance testing",children:"Perf"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(an,{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($S,{}),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(Xz,{}),l.jsx("div",{className:"tab-panels",children:i.map(g=>l.jsx("div",{className:`tab-panel${g===s?" active":""}`,children:l.jsx(Kz,{sessionId:g})},g))})]})})]}),f&&l.jsx(sW,{onClose:()=>d(!1)}),m&&l.jsx(dW,{onClose:()=>p(!1)}),v&&l.jsx(hW,{onClose:()=>y(!1)})]})}function pW(){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=al(),m=pn();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(an,{to:"/register",children:"Register"})]})]})})}function vW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),c=al(),u=pn();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 gW(){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=pn();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 x=await g.json().catch(()=>({}));throw new Error(x.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"?Vw:Jw,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(an,{to:"/login",children:"Sign in"})]})]})})}const yW=["accent","bg","bg2","bg3","border","text","text2"];function Qw(e){let t=document.getElementById("branding-style");t||(t=document.createElement("style"),t.id="branding-style",document.head.appendChild(t));const r=yW.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 Bl(e){return new Promise((t,r)=>{const n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}const ej=["user","user_admin","platform_admin"];function bW(e){return new Date(e*1e3).toLocaleString()}function xW({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 Z.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:ej.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 wW({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 Z.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 jW(){const e=X(I=>I.user),t=pn(),[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,x]=h.useState(!1),[w,j]=h.useState(null),[S,_]=h.useState("");async function O(){try{const[I,F,W,G]=await Promise.all([Z.admin.users.list(),Z.admin.settings.get(),Z.admin.housekeeping.get(),Z.admin.branding.get()]);n(I),i(F),o(W),p(G)}catch(I){_(I.message)}}h.useEffect(()=>{O()},[]);async function P(I){try{const F=await Z.admin.users.update(I.uid,{is_active:!I.is_active});n(W=>W.map(G=>G.uid===I.uid?F:G))}catch(F){_(F.message)}}async function E(I,F){try{const W=await Z.admin.users.update(I.uid,{role:F});n(G=>G.map(J=>J.uid===I.uid?W:J))}catch(W){_(W.message)}}async function k(I){if(confirm(`Delete user "${I.username}"? This cannot be undone.`))try{await Z.admin.users.delete(I.uid),n(F=>F.filter(W=>W.uid!==I.uid))}catch(F){_(F.message)}}async function T(){if(a)try{const I=await Z.admin.settings.update({registration_enabled:!a.enabled});i(I)}catch(I){_(I.message)}}async function C(I){const F=parseInt(I,10);if(!(isNaN(F)||F<1))try{const W=await Z.admin.settings.update({max_users:F});i(W)}catch(W){_(W.message)}}async function M(){if(s)try{const I=await Z.admin.housekeeping.update({enabled:!s.enabled});o(I)}catch(I){_(I.message)}}async function $(I,F){const W=parseInt(F,10);if(!(isNaN(W)||W<0))try{const G=await Z.admin.housekeeping.update({[I]:W});o(G)}catch(G){_(G.message)}}async function U(){d(!0),u(null);try{const I=await Z.admin.housekeeping.runNow();u(`Done — deleted ${I.msgs_deleted} messages, ${I.logs_deleted} log files`)}catch(I){_(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:()=>x(!0),children:"+ Create User"})]}),S&&l.jsx("div",{className:"error-msg",style:{margin:"0 0 12px"},children:S}),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:M,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=>$("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=>$("log_retention_days",I.target.value)}),l.jsx("button",{className:"btn btn-sm btn-ghost",style:{marginLeft:16},onClick:U,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,F,W])=>l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[l.jsxs("span",{className:"admin-settings-label",children:[F,":"]}),l.jsx("input",{type:"color",value:m[I]||W,onChange:G=>p({...m,[I]:G.target.value}),title:F}),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 W;const F=(W=I.target.files)==null?void 0:W[0];F&&p({...m,logo_dark:await Bl(F)})}}),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 W;const F=(W=I.target.files)==null?void 0:W[0];F&&p({...m,logo_light:await Bl(F)})}}),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 W;const F=(W=I.target.files)==null?void 0:W[0];F&&p({...m,favicon:await Bl(F)})}}),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 Z.admin.branding.update(m);p(I),g(I),Qw(I)}catch(I){_(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:F=>E(I,F.target.value),className:"admin-role-select",disabled:I.uid===(e==null?void 0:e.uid),children:ej.map(F=>l.jsx("option",{value:F,children:F},F))})}),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:bW(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(xW,{onClose:()=>x(!1),onCreated:I=>n(F=>[...F,I])}),w&&l.jsx(wW,{user:w,onClose:()=>j(null)})]})}const Uc={enabled:!1,every_n_fills:1,amend_ratio:1,max_per_order:1,price_offset:.01,pending_replace:!1,amend_wait_ms:2e3},Hc={enabled:!1,after_fills:0,cancel_ratio:1,venue_response:"reject",cxl_rej_reason:0},uv={inject_delay_ms:0,jitter_ms:0},dv={name:"smoke",mode:"loopback",client_session_id:null,venue_session_id:null,relay_in_session_id:null,relay_out_session_id:null,relay:{inject_delay_ms:0,jitter_ms:0},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,max_msgs_per_sec:null,ceiling_session_id:null},test:{duration:30,max_orders:0,scenario_timeout_ms:3e4,max_pending:1e5,on_saturation:"pause",output:"results",record_latency:!0},amend:Uc,cancel:Hc,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:{}}},_W=["loopback","manager","client","venue","middle_manager"];function fv(e,t){return t?Math.round(e/t*1e3):0}function SW({onStarted:e}){var er,ua;const t=X(N=>N.sessions),[r,n]=h.useState(dv),[a,i]=h.useState(dv.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([]),x=Object.values(t),w=r.mode==="middle_manager",j=r.mode==="loopback"||r.mode==="manager"||r.mode==="client"||w,S=r.mode==="loopback"||r.mode==="manager"||r.mode==="venue"||w,_=r.relay??uv,O=r.amend??Uc,P=r.cancel??Hc,E=Math.max(1,2+r.payload.fill.fills_per_order+(O.enabled?2:0)+(P.enabled?2:0)),k=r.client_session_id?t[r.client_session_id]:void 0,T=r.venue_session_id?t[r.venue_session_id]:void 0,C=!j||(k==null?void 0:k.status)==="LOGGED_ON",M=!S||(T==null?void 0:T.status)==="LOGGED_ON",$=(!j||!!r.client_session_id)&&(!S||!!r.venue_session_id)&&(!w||!!r.relay_in_session_id&&!!r.relay_out_session_id),U=(()=>{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(j){if(N.orders_per_window<1||N.order_window_ms<1)return"Order rate must be > 0";if(a.split(",").map(lt=>lt.trim()).filter(Boolean).length===0)return"At least one symbol is required";const H=r.payload.order;if(H.quantity_min<1||H.quantity_max<1||H.quantity_min>H.quantity_max)return"Check quantity min/max";if(H.price_min<=0||H.price_max<=0||H.price_min>H.price_max)return"Check price min/max";if(O.enabled){if(O.every_n_fills<1)return"Amend every-n-fills must be ≥ 1";if(O.amend_ratio<0||O.amend_ratio>1)return"Amend ratio must be between 0 and 1";if(O.max_per_order<0)return"Max amends per order must be ≥ 0";if(O.price_offset<=0)return"Amend price offset must be > 0";if((O.amend_wait_ms??2e3)<1)return"Amend wait must be > 0"}if(P.enabled){if(P.after_fills<0)return"Cancel after-fills must be ≥ 0";if(P.cancel_ratio<0||P.cancel_ratio>1)return"Cancel ratio must be between 0 and 1";if(P.cxl_rej_reason<0)return"Cancel reject reason must be ≥ 0"}}if(S){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(!P.enabled)return"Fills per order 0 (ack-only) requires Cancels enabled";if(P.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"}if(w){if(!r.relay_in_session_id||!r.relay_out_session_id)return"Select both relay sessions";const H=[r.client_session_id,r.relay_in_session_id,r.relay_out_session_id,r.venue_session_id];if(new Set(H).size!==4)return"middle_manager needs four distinct sessions";if(_.inject_delay_ms<0||_.jitter_ms<0)return"Relay delay/jitter must be ≥ 0"}if(N.max_msgs_per_sec!=null){if(N.max_msgs_per_sec<1)return"Max messages/sec must be ≥ 1";const H=[j?r.client_session_id:null,S?r.venue_session_id:null,w?r.relay_in_session_id:null,w?r.relay_out_session_id:null].filter(Boolean);if(!N.ceiling_session_id)return"Choose which session the messages/sec ceiling protects";if(!H.includes(N.ceiling_session_id))return"Ceiling session must be one this run drives"}return null})(),I=$&&C&&M&&!U&&!f;async function F(){try{p(await Z.perf.configs.list())}catch{}}h.useEffect(()=>{F()},[]),h.useEffect(()=>{Z.templates.list().then(b).catch(()=>{})},[]);const W=g.filter(N=>N.msg_type==="D"),G=g.filter(N=>N.msg_type==="8"),J=!!r.payload.order_template_id,le=!!r.payload.exec_template_id,oe=((er=W.find(N=>N.id===r.payload.order_template_id))==null?void 0:er.name)??"selected",Ce=((ua=G.find(N=>N.id===r.payload.exec_template_id))==null?void 0:ua.name)??"selected",ce=N=>n(H=>({...H,...N})),me=N=>n(H=>({...H,rate:{...H.rate,...N}})),L=N=>n(H=>({...H,test:{...H.test,...N}})),te=N=>n(H=>({...H,amend:{...Uc,...H.amend,...N}})),ee=N=>n(H=>({...H,cancel:{...Hc,...H.cancel,...N}})),z=N=>n(H=>({...H,relay:{...uv,...H.relay,...N}})),B=N=>n(H=>({...H,payload:{...H.payload,order:{...H.payload.order,...N}}})),Q=N=>n(H=>({...H,payload:{...H.payload,fill:{...H.payload.fill,...N}}})),R=N=>n(H=>({...H,payload:{...H.payload,...N}})),[Ne,ie]=h.useState([]),Ee=N=>{ie(N),R({gen_rules:Object.fromEntries(N.filter(H=>H.tag.trim()).map(H=>[H.tag.trim(),H.value]))})},ke=(N,H)=>Ee(Ne.map((lt,Fr)=>Fr===N?{...lt,...H}:lt));function yt(){const N=a.split(",").map(H=>H.trim()).filter(Boolean);return{...r,client_session_id:j?r.client_session_id:null,venue_session_id:S?r.venue_session_id:null,relay_in_session_id:w?r.relay_in_session_id:null,relay_out_session_id:w?r.relay_out_session_id:null,payload:{...r.payload,order:{...r.payload.order,symbols:N}}}}async function xr(){u(""),d(!0);try{const{run_id:N}=await Z.perf.runs.create(yt());e(N)}catch(N){u(N.message??"Failed to start run")}finally{d(!1)}}async function _n(){u("");try{await Z.perf.configs.save(yt()),await F()}catch(N){u(N.message??"Failed to save config")}}async function yi(N){if(y(N),!!N){u("");try{const H=await Z.perf.configs.get(N);n(H),i(H.payload.order.symbols.join(", ")),ie(Object.entries(H.payload.gen_rules??{}).map(([lt,Fr])=>({tag:lt,value:Fr})))}catch(H){u(H.message??"Failed to load config")}}}async function il(){if(v&&window.confirm("Delete this saved config?"))try{await Z.perf.configs.delete(v),y(""),await F()}catch(N){u(N.message??"Failed to delete config")}}const wr=N=>{const H=t[N];return H?`${H.display_name||`${H.sender_comp_id} → ${H.target_comp_id}`} (${H.status})`:N},bi=x.filter(N=>N.session_role!=="venue"),jr=x.filter(N=>N.session_role!=="client"),ca=x.filter(N=>N.connection_type==="acceptor"),sl=x.filter(N=>N.connection_type==="initiator");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=>yi(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:_n,children:"Save current"}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:il,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=>ce({name:N.target.value})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Mode"}),l.jsx("select",{value:r.mode,onChange:N=>ce({mode:N.target.value}),children:_W.map(N=>l.jsx("option",{value:N,children:N},N))})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Client session"}),l.jsxs("select",{value:r.client_session_id??"",onChange:N=>ce({client_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),bi.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]}),k&&l.jsx(hv,{status:k.status})]}),S&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue session"}),l.jsxs("select",{value:r.venue_session_id??"",onChange:N=>ce({venue_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),jr.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]}),T&&l.jsx(hv,{status:T.status})]}),w&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Relay in (acceptor ← client)"}),l.jsxs("select",{value:r.relay_in_session_id??"",onChange:N=>ce({relay_in_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),ca.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Relay out (initiator → venue)"}),l.jsxs("select",{value:r.relay_out_session_id??"",onChange:N=>ce({relay_out_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),sl.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]})]}),l.jsx("div",{className:"perf-hint",children:"middle_manager splices a 2-session relay between the gen-client and venue: client → relay-in → relay-out → venue. The relay forwards verbatim and times its own pass-through. All four sessions must be LOGGED_ON and distinct."})]})]}),w&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Relay"}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Inject delay (ms)"}),l.jsx("input",{type:"number",min:0,value:_.inject_delay_ms,onChange:N=>z({inject_delay_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Jitter (ms)"}),l.jsx("input",{type:"number",min:0,value:_.jitter_ms,onChange:N=>z({jitter_ms:Number(N.target.value)})})]})]}),l.jsx("div",{className:"perf-hint",children:"Verbatim forward by default. An injected delay (+ random jitter) before each forward simulates a slow hub hop — it shows up in the relay pass-through latency and in the end-to-end latencies, not the venue's."})]}),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:[j&&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=>me({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=>me({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=>me({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)"})]})]})]}),S&&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=>me({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=>me({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=>me({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=>me({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-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max messages/sec (optional)"}),l.jsx("input",{type:"number",min:0,value:r.rate.max_msgs_per_sec??0,onChange:N=>{const H=Number(N.target.value);me(H>0?{max_msgs_per_sec:H}:{max_msgs_per_sec:null,ceiling_session_id:null})}})]}),r.rate.max_msgs_per_sec!=null&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Ceiling session"}),l.jsxs("select",{value:r.rate.ceiling_session_id??"",onChange:N=>me({ceiling_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(choose…)"}),j&&r.client_session_id&&l.jsx("option",{value:r.client_session_id,children:wr(r.client_session_id)}),S&&r.venue_session_id&&l.jsx("option",{value:r.venue_session_id,children:wr(r.venue_session_id)})]})]})]}),r.rate.max_msgs_per_sec!=null&&l.jsxs("div",{className:"perf-hint",children:["Caps total messages the chosen session sends + receives per second by pausing the injector to ≈ ",Math.max(1,Math.round(r.rate.max_msgs_per_sec/E))," orders/s — acks/replies stay immediate, so latency stays clean. Useful to hold a logged/durable session under the rate it can persist."]}),l.jsxs("div",{className:"perf-hint",children:[j&&l.jsxs(l.Fragment,{children:["≈ ",fv(r.rate.orders_per_window,r.rate.order_window_ms).toLocaleString()," orders/s"]}),j&&S&&" · ",S&&l.jsxs(l.Fragment,{children:["≈ ",fv(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=>L({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=>L({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=>L({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=>L({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"})]})]})]}),j&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.test.record_latency,onChange:N=>L({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."]})]}),j&&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:O.enabled,onChange:N=>te({enabled:N.target.checked})}),"Amend working orders — price-only OrderCancelReplaceRequest fired every nth fill"]}),O.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:O.every_n_fills,onChange:N=>te({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:O.amend_ratio,onChange:N=>te({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:O.max_per_order,onChange:N=>te({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:O.price_offset,onChange:N=>te({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:O.amend_wait_ms??2e3,onChange:N=>te({amend_wait_ms:Number(N.target.value)})})]})]}),S&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:O.pending_replace,onChange:N=>te({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."}),S&&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."]})]})]}),j&&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:P.enabled,onChange:N=>ee({enabled:N.target.checked})}),"Cancel orders — one OrderCancelRequest per order, on the ack or the nth fill"]}),P.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:P.after_fills,onChange:N=>ee({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:P.cancel_ratio,onChange:N=>ee({cancel_ratio:Number(N.target.value)})})]}),S&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue response"}),l.jsxs("select",{value:P.venue_response,onChange:N=>ee({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"})]})]}),S&&P.venue_response==="reject"&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"CxlRejReason (102)"}),l.jsx("input",{type:"number",min:0,value:P.cxl_rej_reason,onChange:N=>ee({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)."]}),S&&P.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."]})]})]}),j&&l.jsxs("div",{className:`perf-form-section ${J?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Order payload",J&&l.jsx("span",{className:"perf-layer-badge",children:"values for template"})]}),J&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Order template ",l.jsx("strong",{children:oe})," 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=>B({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 ${J?"perf-field-overridden":""}`,children:[l.jsxs("span",{children:["Order type ",J&&l.jsx("em",{className:"perf-field-tag",children:"from template"})]}),l.jsxs("select",{value:r.payload.order.ord_type,disabled:J,onChange:N=>B({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=>B({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=>B({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=>B({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=>B({price_max:Number(N.target.value)})})]})]})]}),j&&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=>R({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=>R({expiry_offset:Number(N.target.value)})}),l.jsxs("select",{value:r.payload.expiry_unit??"days",onChange:N=>R({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"})]})]}),Ne.map((N,H)=>l.jsxs("div",{className:"perf-field-row",children:[l.jsx("input",{style:{width:72},placeholder:"tag",value:N.tag,onChange:lt=>ke(H,{tag:lt.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:lt=>ke(H,{value:lt.target.value})}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:()=>Ee(Ne.filter((lt,Fr)=>Fr!==H)),children:"✕"})]},H)),l.jsx("div",{className:"perf-field-row",children:l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Ee([...Ne,{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."]})]}),S&&l.jsxs("div",{className:`perf-form-section ${le?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Fill payload",le&&l.jsx("span",{className:"perf-layer-badge",children:"behavior for template"})]}),le&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Exec template ",l.jsx("strong",{children:Ce})," 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=>Q({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=>Q({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=>Q({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=>Q({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=>Q({partial_fill_pct_max:Number(N.target.value)})})]})]})]})]}),(j||S)&&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:[j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order template"}),l.jsxs("select",{value:r.payload.order_template_id??"",onChange:N=>R({order_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),W.map(N=>l.jsx("option",{value:N.id,children:N.name},N.id))]})]}),S&&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=>R({exec_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),G.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=>ce({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=>ce({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=>L({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=>me({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=>me({max_burst_multiplier:Number(N.target.value)})})]})]})]}),l.jsxs("div",{className:"perf-form-actions",children:[U?l.jsx("span",{className:"perf-not-ready",children:U}):$&&!(C&&M)?l.jsxs("span",{className:"perf-not-ready",children:["Selected session",j&&S?"s":""," must be LOGGED_ON before starting."]}):null,l.jsx("button",{className:"btn btn-primary",onClick:xr,disabled:!I,title:$?U||(C&&M?void 0:"Session not logged on yet"):"Select the required session(s)",children:f?"Starting…":"▶ Start run"})]})]})}function hv({status:e}){const t=e==="LOGGED_ON";return l.jsxs("span",{className:`perf-status-chip ${t?"ok":"warn"}`,children:[t?"● ":"○ ",e]})}const mv=600,OW=5e3,NW=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 Z.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 x;try{x=JSON.parse(b.data)}catch{return}r(x),a(w=>{var _,O,P,E,k,T,C,M,$,U,I,F,W,G,J,le;const j={t:x.elapsed_s,ops:((_=x.client)==null?void 0:_.ops_live)??0,fps:((O=x.venue)==null?void 0:O.fps_live)??0,aps:((P=x.client)==null?void 0:P.aps_live)??0,cps:((E=x.client)==null?void 0:E.cps_live)??0,crps:((k=x.client)==null?void 0:k.crps_live)??0,ccps:((T=x.client)==null?void 0:T.ccps_live)??0,mco:((C=x.client)==null?void 0:C.msgs_out_live)??0,mci:((M=x.client)==null?void 0:M.msgs_in_live)??0,mvo:(($=x.venue)==null?void 0:$.msgs_out_live)??0,mvi:((U=x.venue)==null?void 0:U.msgs_in_live)??0,tps:x.client?x.client.msgs_out_live+x.client.msgs_in_live:(((I=x.venue)==null?void 0:I.msgs_out_live)??0)+(((F=x.venue)==null?void 0:F.msgs_in_live)??0),p50:(((W=x.response_latency)==null?void 0:W.p50_us)??0)/1e3,p99:(((G=x.response_latency)==null?void 0:G.p99_us)??0)/1e3,fc50:(((J=x.fill_completion_latency)==null?void 0:J.p50_us)??0)/1e3,fc99:(((le=x.fill_completion_latency)==null?void 0:le.p99_us)??0)/1e3},S=[...w,j];return S.length>mv?S.slice(-mv):S}),NW.has(x.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(OW,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 Gs="img-export-exclude";async function CW(e,t,r){const{toPng:n,toJpeg:a}=await Kv(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(Gs))}),c=document.createElement("a");c.href=o,c.download=t,c.click()}function EW(e,t){const r=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");return`${e}-${r}.${t}`}const zl=new Set(["completed","stopped","error"]);function Wt(e){return e?(e/1e3).toFixed(3):"—"}function tj(e,t){return t<=0?"—":(e/t).toFixed(2)}function kn({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 AW({runId:e}){var _,O,P,E,k,T,C;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 M=!0;i(null);let $;const U=async()=>{try{const I=await Z.perf.runs.get(e);if(!M)return;i(I),zl.has(I.status)&&clearInterval($)}catch{}};return $=setInterval(U,2e3),U(),()=>{M=!1,clearInterval($)}},[e]);const d=t?zl.has(t.status):a?zl.has(a.status):!1,m=!d;async function p(){try{await Z.perf.runs.stop(e)}catch{}}async function v(M){if(!(!f.current||s)){o(!0);try{const $=M==="png"?"png":"jpg";await CW(f.current,EW(`perf-${e.slice(0,8)}`,$),M)}catch($){console.error("image export failed",$)}finally{o(!1)}}}const y=t==null?void 0:t.client,g=t==null?void 0:t.venue,b=t==null?void 0:t.relay,x=t==null?void 0:t.errors,w=(x==null?void 0:x.saturated)||(t==null?void 0:t.status)==="saturated",j=t==null?void 0:t.rate_config,S=[];return y&&(S.push({label:"orders/s",value:Math.round(y.ops_live).toLocaleString()}),(j==null?void 0:j.orders_per_window)!=null&&(j==null?void 0:j.order_window_ms)!=null&&S.push({label:"order rate (cfg)",value:`${j.orders_per_window.toLocaleString()} / ${j.order_window_ms}ms`}),S.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)&&(S.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&&S.push({label:"amends lost",value:y.amends_lost.toLocaleString()})),y.amend_rejects>0&&S.push({label:"amend rejects (35=9)",value:y.amend_rejects.toLocaleString()}),(y.cancels_sent??0)>0&&(S.push({label:"cancels (35=F)",value:y.cancels_sent.toLocaleString()}),(y.cancel_rejects??0)>0&&S.push({label:"cancel rejects (35=9)",value:y.cancel_rejects.toLocaleString()}),(y.cancels_canceled??0)>0&&S.push({label:"canceled (150=4)",value:y.cancels_canceled.toLocaleString()}),(y.cancels_lost??0)>0&&S.push({label:"cancels lost",value:y.cancels_lost.toLocaleString()}))),g&&(S.push({label:"fills/s",value:Math.round(g.fps_live).toLocaleString()}),(j==null?void 0:j.fills_per_window)!=null&&(j==null?void 0:j.fill_window_ms)!=null&&S.push({label:"fill rate (cfg)",value:`${j.fills_per_window.toLocaleString()} / ${j.fill_window_ms}ms`}),S.push({label:"fills",value:g.fills_sent.toLocaleString()},{label:"fills/order",value:tj(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&&S.push({label:"amends recv",value:g.amends_received.toLocaleString()}),g.amends_uncorrelated>0&&S.push({label:"amends no-corr",value:g.amends_uncorrelated.toLocaleString()}),g.amend_waits_expired>0&&S.push({label:"amend waits expired",value:g.amend_waits_expired.toLocaleString()}),(g.cancels_received??0)>0&&S.push({label:"cancels recv",value:g.cancels_received.toLocaleString()}),(g.cancels_uncorrelated??0)>0&&S.push({label:"cancels no-corr",value:g.cancels_uncorrelated.toLocaleString()})),b&&(S.push({label:"relay ↓/s",value:Math.round(b.mdps_live).toLocaleString()},{label:"relay ↑/s",value:Math.round(b.mups_live).toLocaleString()},{label:"relayed ↓",value:b.msgs_down.toLocaleString()},{label:"relayed ↑",value:b.msgs_up.toLocaleString()}),b.dropped>0&&S.push({label:"relay dropped",value:b.dropped.toLocaleString()})),x&&S.push({label:"lost",value:x.lost_timeout.toLocaleString()},{label:"rejected",value:x.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 ${Gs}`,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:()=>Z.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:()=>Z.perf.runs.export(e,"latency"),children:"Latency CSV"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Z.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((M,$)=>l.jsxs("div",{className:"perf-warning-banner",children:["⚠ ",M]},$)),!t&&!d&&l.jsx("div",{className:"loading-msg",children:"Waiting for first snapshot…"}),S.length>0&&l.jsx("div",{className:"stat-cards",children:S.map(({label:M,value:$})=>l.jsxs("div",{className:"stat-card",children:[l.jsx("span",{className:"stat-card-value",children:$}),l.jsx("span",{className:"stat-card-label",children:M})]},M))}),t&&l.jsxs("div",{className:"perf-lat-row",children:[l.jsx(kn,{title:"Response latency",sub:"order-2-ack",stats:t.response_latency}),l.jsx(kn,{title:"Fill-completion latency",sub:"order-2-last-fill",stats:t.fill_completion_latency}),((O=t.amend_latency)==null?void 0:O.count)>0&&l.jsx(kn,{title:"Amend latency",sub:"amend-2-replaced",stats:t.amend_latency}),((P=t.cancel_latency)==null?void 0:P.count)>0&&l.jsx(kn,{title:"Cancel latency",sub:"cancel-2-reply",stats:t.cancel_latency}),(((k=(E=t.relay)==null?void 0:E.down_latency)==null?void 0:k.count)??0)>0&&l.jsx(kn,{title:"Relay pass-through ↓",sub:"client→venue hop",stats:t.relay.down_latency}),(((C=(T=t.relay)==null?void 0:T.up_latency)==null?void 0:C.count)??0)>0&&l.jsx(kn,{title:"Relay pass-through ↑",sub:"venue→client hop",stats:t.relay.up_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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),l.jsx(Me,{type:"monotone",dataKey:"ops",name:"orders/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"fps",name:"fills/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.amends_sent)??0)>0&&l.jsx(Me,{type:"monotone",dataKey:"aps",name:"amends/s",stroke:"#c084fc",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancels_sent)??0)>0&&l.jsx(Me,{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(Me,{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(Me,{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:Gs,value:c,onChange:M=>u(M.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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),c==="direction"?l.jsxs(l.Fragment,{children:[l.jsx(Me,{type:"monotone",dataKey:"tps",name:"total msgs/s",stroke:"#fbbf24",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"mco",name:"sent/s (requests)",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"mci",name:"received/s (replies)",stroke:"#a78bfa",dot:!1,isAnimationActive:!1})]}):l.jsxs(l.Fragment,{children:[l.jsx(Me,{type:"monotone",dataKey:M=>M.mco+M.mci,name:"client session/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:M=>M.mvo+M.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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),l.jsx(Me,{type:"monotone",dataKey:"p50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Me,{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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),l.jsx(Me,{type:"monotone",dataKey:"fc50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"fc99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})})]}),d&&l.jsx(kW,{runId:e}),d&&(a==null?void 0:a.summary)&&l.jsx(IW,{status:a})]})}function kW({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),Z.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 ${Gs}`,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 IW({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)",tj(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 TW=new Set(["completed","stopped","error"]);function MW(e){if(!e)return"—";try{return new Date(e*1e3).toLocaleTimeString()}catch{return"—"}}function DW({activeRunId:e,onSelect:t,refreshKey:r}){const[n,a]=h.useState([]);h.useEffect(()=>{let s=!0;const o=()=>Z.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 Z.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:MW(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:!TW.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 Wl=200,Rn=(e,t,r=120)=>({field:e,headerName:t,width:r,type:"numericColumn",valueFormatter:n=>n.value==null?"":Number(n.value).toLocaleString()}),Gc=(e,t)=>({field:e,headerName:t,width:150,type:"numericColumn",valueFormatter:r=>r.value==null?"":`${Math.round(Number(r.value)).toLocaleString()}`}),RW=[{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},Rn("qty","Qty",90),Rn("price","Price",100),Gc("response_latency_us","Resp µs"),Gc("fill_latency_us","Fill µs"),Rn("fill_qty","Fill qty",100),Rn("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}],$W=[{field:"scenario_name",headerName:"Scenario",flex:1,minWidth:160},Gc("latency_us","Latency µs"),Rn("msg_count","Msgs",90),Rn("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 LW({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"?Z.perf.runs.messages:Z.perf.runs.scenarios)(e,{limit:Wl,offset:a*Wl}).then(x=>{g&&(o(x.items),u(x.total))}).catch(()=>{g&&(o([]),u(0))}).finally(()=>{g&&d(!1)}),()=>{g=!1}},[e,r,a]);const m=h.useMemo(()=>r==="messages"?RW:$W,[r]),p=h.useMemo(()=>({sortable:!0,filter:!0,resizable:!0}),[]),v=Math.max(0,Math.ceil(c/Wl)-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 FW(){const{theme:e,toggleTheme:t,branding:r,setSessions:n}=X(),a=al(),i=r!=null&&r.prefix?`${r.prefix} FIXture`:"FIXture";Uv(),h.useEffect(()=>{Z.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(an,{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(AW,{runId:c},c)}),s==="results"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(LW,{runId:c},c)}),s==="history"&&l.jsx("div",{className:"perf-pane",children:l.jsx(DW,{activeRunId:c,onSelect:p,refreshKey:f})})]})]})}function BW(){return X(t=>t.token)?l.jsx(kv,{}):l.jsx(Ki,{to:"/login",replace:!0})}function zW(){const e=X(t=>t.user);return e?e.role!=="platform_admin"?l.jsx(Ki,{to:"/",replace:!0}):l.jsx(kv,{}):l.jsx(Ki,{to:"/login",replace:!0})}function WW(){const e=X(t=>t.setBranding);h.useEffect(()=>{fetch("/api/branding").then(t=>t.json()).then(t=>{e(t),Qw(t)}).catch(()=>{})},[])}function UW(){const e=pn(),t=X(r=>r.theme);return WW(),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(d_,{children:[l.jsx(zt,{path:"/setup",element:l.jsx(vW,{})}),l.jsx(zt,{path:"/login",element:l.jsx(pW,{})}),l.jsx(zt,{path:"/register",element:l.jsx(gW,{})}),l.jsxs(zt,{element:l.jsx(BW,{}),children:[l.jsx(zt,{path:"/",element:l.jsx(mW,{})}),l.jsxs(zt,{element:l.jsx(zW,{}),children:[l.jsx(zt,{path:"/admin/users",element:l.jsx(jW,{})}),l.jsx(zt,{path:"/perf",element:l.jsx(FW,{})})]})]}),l.jsx(zt,{path:"*",element:l.jsx(Ki,{to:"/",replace:!0})})]})}Ul.createRoot(document.getElementById("root")).render(l.jsx(pv.StrictMode,{children:l.jsx(D_,{children:l.jsx(UW,{})})}));
|
|
102
|
+
}`;function hW({onClose:e}){const[t,r]=h.useState(""),[n,a]=h.useState(""),[i,s]=h.useState(!1);h.useEffect(()=>{Z.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 Z.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:fW})}),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 Vw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgCgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGNpcmNsZSBjeD0iMjA3IiBjeT0iNzgiIHI9IjEwIiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjY1IiB4Mj0iMjQ4IiB5Mj0iNjUiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDAsIDE4MCwgMjAwKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMCwgMTgwLCAyMDApO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNztmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI5MSIgeDI9IjI0NCIgeTI9IjkxIiBzdHJva2U9IiMwMGI0YzgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjQ1IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigwLCAxODAsIDIwMCk7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC40NTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgoKICA8dGV4dCB4PSIyNzIiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtbW9ubykiIGZvbnQtc2l6ZT0iNjYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6NTBweDtmb250LXdlaWdodDozMDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byI+dHVyZTwvdGV4dD4KPC9zdmc+",Jw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSIxMCIgZmlsbD0iIzFhM2E1YyIgc3R5bGU9ImZpbGw6cmdiKDI2LCA1OCwgOTIpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI2NSIgeDI9IjI0OCIgeTI9IjY1IiBzdHJva2U9IiMxYTNhNWMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigyNiwgNTgsIDkyKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMjYsIDU4LCA5Mik7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC43O2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjkxIiB4Mj0iMjQ0IiB5Mj0iOTEiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNDUiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDI2LCA1OCwgOTIpO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNDU7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJnF1b3Q7U2Vnb2UgVUkmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7Zm9udC13ZWlnaHQ6NDAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iLz4KCiAgPHRleHQgeD0iMjcyIiB5PSIxMDUiIGZvbnQtZmFtaWx5PSJ2YXIoLS1mb250LW1vbm8pIiBmb250LXNpemU9IjY2IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMWEzYTVjIiBsZXR0ZXItc3BhY2luZz0iLTEiIHN0eWxlPSJmaWxsOnJnYigyNiwgNTgsIDkyKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjUwcHg7Zm9udC13ZWlnaHQ6MzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPnR1cmU8L3RleHQ+Cjwvc3ZnPg==";function al(){const e=X(r=>r.theme),t=X(r=>r.branding);return e==="dark"?(t==null?void 0:t.logo_dark)||Vw:(t==null?void 0:t.logo_light)||Jw}function mW(){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=al(),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 Uv(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{Z.sessions.list().then(e).catch(console.error)},[]),h.useEffect(()=>{if(!n){_S();return}Z.customTags.list().then(Wv).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(an,{to:"/perf",className:"btn btn-sm btn-ghost",title:"Performance testing",children:"Perf"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(an,{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($S,{}),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(Xz,{}),l.jsx("div",{className:"tab-panels",children:i.map(g=>l.jsx("div",{className:`tab-panel${g===s?" active":""}`,children:l.jsx(Kz,{sessionId:g})},g))})]})})]}),f&&l.jsx(sW,{onClose:()=>d(!1)}),m&&l.jsx(dW,{onClose:()=>p(!1)}),v&&l.jsx(hW,{onClose:()=>y(!1)})]})}function pW(){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=al(),m=pn();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(an,{to:"/register",children:"Register"})]})]})})}function vW(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[s,o]=h.useState(!1),c=al(),u=pn();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 gW(){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=pn();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 x=await g.json().catch(()=>({}));throw new Error(x.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"?Vw:Jw,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(an,{to:"/login",children:"Sign in"})]})]})})}const yW=["accent","bg","bg2","bg3","border","text","text2"];function Qw(e){let t=document.getElementById("branding-style");t||(t=document.createElement("style"),t.id="branding-style",document.head.appendChild(t));const r=yW.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 Bl(e){return new Promise((t,r)=>{const n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}const ej=["user","user_admin","platform_admin"];function bW(e){return new Date(e*1e3).toLocaleString()}function xW({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 Z.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:ej.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 wW({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 Z.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 jW(){const e=X(I=>I.user),t=pn(),[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,x]=h.useState(!1),[w,j]=h.useState(null),[S,_]=h.useState("");async function O(){try{const[I,F,W,G]=await Promise.all([Z.admin.users.list(),Z.admin.settings.get(),Z.admin.housekeeping.get(),Z.admin.branding.get()]);n(I),i(F),o(W),p(G)}catch(I){_(I.message)}}h.useEffect(()=>{O()},[]);async function P(I){try{const F=await Z.admin.users.update(I.uid,{is_active:!I.is_active});n(W=>W.map(G=>G.uid===I.uid?F:G))}catch(F){_(F.message)}}async function E(I,F){try{const W=await Z.admin.users.update(I.uid,{role:F});n(G=>G.map(J=>J.uid===I.uid?W:J))}catch(W){_(W.message)}}async function k(I){if(confirm(`Delete user "${I.username}"? This cannot be undone.`))try{await Z.admin.users.delete(I.uid),n(F=>F.filter(W=>W.uid!==I.uid))}catch(F){_(F.message)}}async function T(){if(a)try{const I=await Z.admin.settings.update({registration_enabled:!a.enabled});i(I)}catch(I){_(I.message)}}async function C(I){const F=parseInt(I,10);if(!(isNaN(F)||F<1))try{const W=await Z.admin.settings.update({max_users:F});i(W)}catch(W){_(W.message)}}async function M(){if(s)try{const I=await Z.admin.housekeeping.update({enabled:!s.enabled});o(I)}catch(I){_(I.message)}}async function $(I,F){const W=parseInt(F,10);if(!(isNaN(W)||W<0))try{const G=await Z.admin.housekeeping.update({[I]:W});o(G)}catch(G){_(G.message)}}async function U(){d(!0),u(null);try{const I=await Z.admin.housekeeping.runNow();u(`Done — deleted ${I.msgs_deleted} messages, ${I.logs_deleted} log files`)}catch(I){_(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:()=>x(!0),children:"+ Create User"})]}),S&&l.jsx("div",{className:"error-msg",style:{margin:"0 0 12px"},children:S}),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:M,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=>$("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=>$("log_retention_days",I.target.value)}),l.jsx("button",{className:"btn btn-sm btn-ghost",style:{marginLeft:16},onClick:U,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,F,W])=>l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[l.jsxs("span",{className:"admin-settings-label",children:[F,":"]}),l.jsx("input",{type:"color",value:m[I]||W,onChange:G=>p({...m,[I]:G.target.value}),title:F}),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 W;const F=(W=I.target.files)==null?void 0:W[0];F&&p({...m,logo_dark:await Bl(F)})}}),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 W;const F=(W=I.target.files)==null?void 0:W[0];F&&p({...m,logo_light:await Bl(F)})}}),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 W;const F=(W=I.target.files)==null?void 0:W[0];F&&p({...m,favicon:await Bl(F)})}}),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 Z.admin.branding.update(m);p(I),g(I),Qw(I)}catch(I){_(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:F=>E(I,F.target.value),className:"admin-role-select",disabled:I.uid===(e==null?void 0:e.uid),children:ej.map(F=>l.jsx("option",{value:F,children:F},F))})}),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:bW(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(xW,{onClose:()=>x(!1),onCreated:I=>n(F=>[...F,I])}),w&&l.jsx(wW,{user:w,onClose:()=>j(null)})]})}const Uc={enabled:!1,every_n_fills:1,amend_ratio:1,max_per_order:1,price_offset:.01,pending_replace:!1,amend_wait_ms:2e3},Hc={enabled:!1,after_fills:0,cancel_ratio:1,venue_response:"reject",cxl_rej_reason:0},uv={inject_delay_ms:0,jitter_ms:0},dv={name:"smoke",mode:"loopback",client_session_id:null,venue_session_id:null,relay_in_session_id:null,relay_out_session_id:null,relay:{inject_delay_ms:0,jitter_ms:0},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,max_msgs_per_sec:null,ceiling_session_id:null},test:{duration:30,max_orders:0,scenario_timeout_ms:3e4,max_pending:1e5,on_saturation:"pause",output:"results",record_latency:!0},amend:Uc,cancel:Hc,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:{}}},_W=["loopback","manager","client","venue","middle_manager"];function fv(e,t){return t?Math.round(e/t*1e3):0}function SW({onStarted:e}){var er,ua;const t=X(N=>N.sessions),[r,n]=h.useState(dv),[a,i]=h.useState(dv.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([]),x=Object.values(t),w=r.mode==="middle_manager",j=r.mode==="loopback"||r.mode==="manager"||r.mode==="client"||w,S=r.mode==="loopback"||r.mode==="manager"||r.mode==="venue"||w,_=r.relay??uv,O=r.amend??Uc,P=r.cancel??Hc,E=Math.max(1,2+r.payload.fill.fills_per_order+(O.enabled?2:0)+(P.enabled?2:0)),k=r.client_session_id?t[r.client_session_id]:void 0,T=r.venue_session_id?t[r.venue_session_id]:void 0,C=!j||(k==null?void 0:k.status)==="LOGGED_ON",M=!S||(T==null?void 0:T.status)==="LOGGED_ON",$=(!j||!!r.client_session_id)&&(!S||!!r.venue_session_id)&&(!w||!!r.relay_in_session_id&&!!r.relay_out_session_id),U=(()=>{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(j){if(N.orders_per_window<1||N.order_window_ms<1)return"Order rate must be > 0";if(a.split(",").map(lt=>lt.trim()).filter(Boolean).length===0)return"At least one symbol is required";const H=r.payload.order;if(H.quantity_min<1||H.quantity_max<1||H.quantity_min>H.quantity_max)return"Check quantity min/max";if(H.price_min<=0||H.price_max<=0||H.price_min>H.price_max)return"Check price min/max";if(O.enabled){if(O.every_n_fills<1)return"Amend every-n-fills must be ≥ 1";if(O.amend_ratio<0||O.amend_ratio>1)return"Amend ratio must be between 0 and 1";if(O.max_per_order<0)return"Max amends per order must be ≥ 0";if(O.price_offset<=0)return"Amend price offset must be > 0";if((O.amend_wait_ms??2e3)<1)return"Amend wait must be > 0"}if(P.enabled){if(P.after_fills<0)return"Cancel after-fills must be ≥ 0";if(P.cancel_ratio<0||P.cancel_ratio>1)return"Cancel ratio must be between 0 and 1";if(P.cxl_rej_reason<0)return"Cancel reject reason must be ≥ 0"}}if(S){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(!P.enabled)return"Fills per order 0 (ack-only) requires Cancels enabled";if(P.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"}if(w){if(!r.relay_in_session_id||!r.relay_out_session_id)return"Select both relay sessions";const H=[r.client_session_id,r.relay_in_session_id,r.relay_out_session_id,r.venue_session_id];if(new Set(H).size!==4)return"middle_manager needs four distinct sessions";if(_.inject_delay_ms<0||_.jitter_ms<0)return"Relay delay/jitter must be ≥ 0"}if(N.max_msgs_per_sec!=null){if(N.max_msgs_per_sec<1)return"Max messages/sec must be ≥ 1";const H=[j?r.client_session_id:null,S?r.venue_session_id:null,w?r.relay_in_session_id:null,w?r.relay_out_session_id:null].filter(Boolean);if(!N.ceiling_session_id)return"Choose which session the messages/sec ceiling protects";if(!H.includes(N.ceiling_session_id))return"Ceiling session must be one this run drives"}return null})(),I=$&&C&&M&&!U&&!f;async function F(){try{p(await Z.perf.configs.list())}catch{}}h.useEffect(()=>{F()},[]),h.useEffect(()=>{Z.templates.list().then(b).catch(()=>{})},[]);const W=g.filter(N=>N.msg_type==="D"),G=g.filter(N=>N.msg_type==="8"),J=!!r.payload.order_template_id,le=!!r.payload.exec_template_id,oe=((er=W.find(N=>N.id===r.payload.order_template_id))==null?void 0:er.name)??"selected",Ce=((ua=G.find(N=>N.id===r.payload.exec_template_id))==null?void 0:ua.name)??"selected",ce=N=>n(H=>({...H,...N})),me=N=>n(H=>({...H,rate:{...H.rate,...N}})),L=N=>n(H=>({...H,test:{...H.test,...N}})),te=N=>n(H=>({...H,amend:{...Uc,...H.amend,...N}})),ee=N=>n(H=>({...H,cancel:{...Hc,...H.cancel,...N}})),z=N=>n(H=>({...H,relay:{...uv,...H.relay,...N}})),B=N=>n(H=>({...H,payload:{...H.payload,order:{...H.payload.order,...N}}})),Q=N=>n(H=>({...H,payload:{...H.payload,fill:{...H.payload.fill,...N}}})),R=N=>n(H=>({...H,payload:{...H.payload,...N}})),[Ne,ie]=h.useState([]),Ee=N=>{ie(N),R({gen_rules:Object.fromEntries(N.filter(H=>H.tag.trim()).map(H=>[H.tag.trim(),H.value]))})},ke=(N,H)=>Ee(Ne.map((lt,Fr)=>Fr===N?{...lt,...H}:lt));function yt(){const N=a.split(",").map(H=>H.trim()).filter(Boolean);return{...r,client_session_id:j?r.client_session_id:null,venue_session_id:S?r.venue_session_id:null,relay_in_session_id:w?r.relay_in_session_id:null,relay_out_session_id:w?r.relay_out_session_id:null,payload:{...r.payload,order:{...r.payload.order,symbols:N}}}}async function xr(){u(""),d(!0);try{const{run_id:N}=await Z.perf.runs.create(yt());e(N)}catch(N){u(N.message??"Failed to start run")}finally{d(!1)}}async function _n(){u("");try{await Z.perf.configs.save(yt()),await F()}catch(N){u(N.message??"Failed to save config")}}async function yi(N){if(y(N),!!N){u("");try{const H=await Z.perf.configs.get(N);n(H),i(H.payload.order.symbols.join(", ")),ie(Object.entries(H.payload.gen_rules??{}).map(([lt,Fr])=>({tag:lt,value:Fr})))}catch(H){u(H.message??"Failed to load config")}}}async function il(){if(v&&window.confirm("Delete this saved config?"))try{await Z.perf.configs.delete(v),y(""),await F()}catch(N){u(N.message??"Failed to delete config")}}const wr=N=>{const H=t[N];return H?`${H.display_name||`${H.sender_comp_id} → ${H.target_comp_id}`} (${H.status})`:N},bi=x.filter(N=>N.session_role!=="venue"),jr=x.filter(N=>N.session_role!=="client"),ca=x.filter(N=>N.connection_type==="acceptor"),sl=x.filter(N=>N.connection_type==="initiator");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=>yi(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:_n,children:"Save current"}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:il,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=>ce({name:N.target.value})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Mode"}),l.jsx("select",{value:r.mode,onChange:N=>ce({mode:N.target.value}),children:_W.map(N=>l.jsx("option",{value:N,children:N},N))})]}),j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Client session"}),l.jsxs("select",{value:r.client_session_id??"",onChange:N=>ce({client_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),bi.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]}),k&&l.jsx(hv,{status:k.status})]}),S&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue session"}),l.jsxs("select",{value:r.venue_session_id??"",onChange:N=>ce({venue_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),jr.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]}),T&&l.jsx(hv,{status:T.status})]}),w&&l.jsxs(l.Fragment,{children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Relay in (acceptor ← client)"}),l.jsxs("select",{value:r.relay_in_session_id??"",onChange:N=>ce({relay_in_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),ca.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Relay out (initiator → venue)"}),l.jsxs("select",{value:r.relay_out_session_id??"",onChange:N=>ce({relay_out_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),sl.map(N=>l.jsx("option",{value:N.session_id,children:wr(N.session_id)},N.session_id))]})]}),l.jsx("div",{className:"perf-hint",children:"middle_manager splices a 2-session relay between the gen-client and venue: client → relay-in → relay-out → venue. The relay forwards verbatim and times its own pass-through. All four sessions must be LOGGED_ON and distinct."})]})]}),w&&l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Relay"}),l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Inject delay (ms)"}),l.jsx("input",{type:"number",min:0,value:_.inject_delay_ms,onChange:N=>z({inject_delay_ms:Number(N.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Jitter (ms)"}),l.jsx("input",{type:"number",min:0,value:_.jitter_ms,onChange:N=>z({jitter_ms:Number(N.target.value)})})]})]}),l.jsx("div",{className:"perf-hint",children:"Verbatim forward by default. An injected delay (+ random jitter) before each forward simulates a slow hub hop — it shows up in the relay pass-through latency and in the end-to-end latencies, not the venue's."})]}),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:[j&&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=>me({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=>me({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=>me({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)"})]})]})]}),S&&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=>me({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=>me({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=>me({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=>me({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-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Max messages/sec (optional)"}),l.jsx("input",{type:"number",min:0,value:r.rate.max_msgs_per_sec??0,onChange:N=>{const H=Number(N.target.value);me(H>0?{max_msgs_per_sec:H}:{max_msgs_per_sec:null,ceiling_session_id:null})}})]}),r.rate.max_msgs_per_sec!=null&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Ceiling session"}),l.jsxs("select",{value:r.rate.ceiling_session_id??"",onChange:N=>me({ceiling_session_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"(choose…)"}),j&&r.client_session_id&&l.jsx("option",{value:r.client_session_id,children:wr(r.client_session_id)}),S&&r.venue_session_id&&l.jsx("option",{value:r.venue_session_id,children:wr(r.venue_session_id)})]})]})]}),r.rate.max_msgs_per_sec!=null&&l.jsxs("div",{className:"perf-hint",children:["Caps total messages the chosen session sends + receives per second by pausing the injector to ≈ ",Math.max(1,Math.round(r.rate.max_msgs_per_sec/E))," orders/s — acks/replies stay immediate, so latency stays clean. Useful to hold a logged/durable session under the rate it can persist."]}),l.jsxs("div",{className:"perf-hint",children:[j&&l.jsxs(l.Fragment,{children:["≈ ",fv(r.rate.orders_per_window,r.rate.order_window_ms).toLocaleString()," orders/s"]}),j&&S&&" · ",S&&l.jsxs(l.Fragment,{children:["≈ ",fv(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=>L({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=>L({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=>L({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=>L({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"})]})]})]}),j&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.test.record_latency,onChange:N=>L({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."]})]}),j&&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:O.enabled,onChange:N=>te({enabled:N.target.checked})}),"Amend working orders — price-only OrderCancelReplaceRequest fired every nth fill"]}),O.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:O.every_n_fills,onChange:N=>te({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:O.amend_ratio,onChange:N=>te({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:O.max_per_order,onChange:N=>te({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:O.price_offset,onChange:N=>te({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:O.amend_wait_ms??2e3,onChange:N=>te({amend_wait_ms:Number(N.target.value)})})]})]}),S&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:O.pending_replace,onChange:N=>te({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."}),S&&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."]})]})]}),j&&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:P.enabled,onChange:N=>ee({enabled:N.target.checked})}),"Cancel orders — one OrderCancelRequest per order, on the ack or the nth fill"]}),P.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:P.after_fills,onChange:N=>ee({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:P.cancel_ratio,onChange:N=>ee({cancel_ratio:Number(N.target.value)})})]}),S&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue response"}),l.jsxs("select",{value:P.venue_response,onChange:N=>ee({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"})]})]}),S&&P.venue_response==="reject"&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"CxlRejReason (102)"}),l.jsx("input",{type:"number",min:0,value:P.cxl_rej_reason,onChange:N=>ee({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)."]}),S&&P.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."]})]})]}),j&&l.jsxs("div",{className:`perf-form-section ${J?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Order payload",J&&l.jsx("span",{className:"perf-layer-badge",children:"values for template"})]}),J&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Order template ",l.jsx("strong",{children:oe})," 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=>B({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 ${J?"perf-field-overridden":""}`,children:[l.jsxs("span",{children:["Order type ",J&&l.jsx("em",{className:"perf-field-tag",children:"from template"})]}),l.jsxs("select",{value:r.payload.order.ord_type,disabled:J,onChange:N=>B({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=>B({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=>B({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=>B({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=>B({price_max:Number(N.target.value)})})]})]})]}),j&&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=>R({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=>R({expiry_offset:Number(N.target.value)})}),l.jsxs("select",{value:r.payload.expiry_unit??"days",onChange:N=>R({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"})]})]}),Ne.map((N,H)=>l.jsxs("div",{className:"perf-field-row",children:[l.jsx("input",{style:{width:72},placeholder:"tag",value:N.tag,onChange:lt=>ke(H,{tag:lt.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:lt=>ke(H,{value:lt.target.value})}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:()=>Ee(Ne.filter((lt,Fr)=>Fr!==H)),children:"✕"})]},H)),l.jsx("div",{className:"perf-field-row",children:l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Ee([...Ne,{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."]})]}),S&&l.jsxs("div",{className:`perf-form-section ${le?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Fill payload",le&&l.jsx("span",{className:"perf-layer-badge",children:"behavior for template"})]}),le&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Exec template ",l.jsx("strong",{children:Ce})," 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=>Q({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=>Q({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=>Q({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=>Q({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=>Q({partial_fill_pct_max:Number(N.target.value)})})]})]})]})]}),(j||S)&&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:[j&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order template"}),l.jsxs("select",{value:r.payload.order_template_id??"",onChange:N=>R({order_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),W.map(N=>l.jsx("option",{value:N.id,children:N.name},N.id))]})]}),S&&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=>R({exec_template_id:N.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),G.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=>ce({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=>ce({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=>L({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=>me({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=>me({max_burst_multiplier:Number(N.target.value)})})]})]})]}),l.jsxs("div",{className:"perf-form-actions",children:[U?l.jsx("span",{className:"perf-not-ready",children:U}):$&&!(C&&M)?l.jsxs("span",{className:"perf-not-ready",children:["Selected session",j&&S?"s":""," must be LOGGED_ON before starting."]}):null,l.jsx("button",{className:"btn btn-primary",onClick:xr,disabled:!I,title:$?U||(C&&M?void 0:"Session not logged on yet"):"Select the required session(s)",children:f?"Starting…":"▶ Start run"})]})]})}function hv({status:e}){const t=e==="LOGGED_ON";return l.jsxs("span",{className:`perf-status-chip ${t?"ok":"warn"}`,children:[t?"● ":"○ ",e]})}const mv=600,OW=5e3,NW=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 Z.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 x;try{x=JSON.parse(b.data)}catch{return}r(x),a(w=>{var _,O,P,E,k,T,C,M,$,U,I,F,W,G,J,le;const j={t:x.elapsed_s,ops:((_=x.client)==null?void 0:_.ops_live)??0,fps:((O=x.venue)==null?void 0:O.fps_live)??0,aps:((P=x.client)==null?void 0:P.aps_live)??0,cps:((E=x.client)==null?void 0:E.cps_live)??0,crps:((k=x.client)==null?void 0:k.crps_live)??0,ccps:((T=x.client)==null?void 0:T.ccps_live)??0,mco:((C=x.client)==null?void 0:C.msgs_out_live)??0,mci:((M=x.client)==null?void 0:M.msgs_in_live)??0,mvo:(($=x.venue)==null?void 0:$.msgs_out_live)??0,mvi:((U=x.venue)==null?void 0:U.msgs_in_live)??0,tps:x.client?x.client.msgs_out_live+x.client.msgs_in_live:(((I=x.venue)==null?void 0:I.msgs_out_live)??0)+(((F=x.venue)==null?void 0:F.msgs_in_live)??0),p50:(((W=x.response_latency)==null?void 0:W.p50_us)??0)/1e3,p99:(((G=x.response_latency)==null?void 0:G.p99_us)??0)/1e3,fc50:(((J=x.fill_completion_latency)==null?void 0:J.p50_us)??0)/1e3,fc99:(((le=x.fill_completion_latency)==null?void 0:le.p99_us)??0)/1e3},S=[...w,j];return S.length>mv?S.slice(-mv):S}),NW.has(x.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(OW,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 Gs="img-export-exclude";async function CW(e,t,r){const{toPng:n,toJpeg:a}=await Kv(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(Gs))}),c=document.createElement("a");c.href=o,c.download=t,c.click()}function EW(e,t){const r=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");return`${e}-${r}.${t}`}const zl=new Set(["completed","stopped","error"]);function Wt(e){return e?(e/1e3).toFixed(3):"—"}function tj(e,t){return t<=0?"—":(e/t).toFixed(2)}function kn({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 AW({runId:e}){var _,O,P,E,k,T,C;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 M=!0;i(null);let $;const U=async()=>{try{const I=await Z.perf.runs.get(e);if(!M)return;i(I),zl.has(I.status)&&clearInterval($)}catch{}};return $=setInterval(U,2e3),U(),()=>{M=!1,clearInterval($)}},[e]);const d=t?zl.has(t.status):a?zl.has(a.status):!1,m=!d;async function p(){try{await Z.perf.runs.stop(e)}catch{}}async function v(M){if(!(!f.current||s)){o(!0);try{const $=M==="png"?"png":"jpg";await CW(f.current,EW(`perf-${e.slice(0,8)}`,$),M)}catch($){console.error("image export failed",$)}finally{o(!1)}}}const y=t==null?void 0:t.client,g=t==null?void 0:t.venue,b=t==null?void 0:t.relay,x=t==null?void 0:t.errors,w=(x==null?void 0:x.saturated)||(t==null?void 0:t.status)==="saturated",j=t==null?void 0:t.rate_config,S=[];return y&&(S.push({label:"orders/s",value:Math.round(y.ops_live).toLocaleString()}),(j==null?void 0:j.orders_per_window)!=null&&(j==null?void 0:j.order_window_ms)!=null&&S.push({label:"order rate (cfg)",value:`${j.orders_per_window.toLocaleString()} / ${j.order_window_ms}ms`}),S.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)&&(S.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&&S.push({label:"amends lost",value:y.amends_lost.toLocaleString()})),y.amend_rejects>0&&S.push({label:"amend rejects (35=9)",value:y.amend_rejects.toLocaleString()}),(y.cancels_sent??0)>0&&(S.push({label:"cancels (35=F)",value:y.cancels_sent.toLocaleString()}),(y.cancel_rejects??0)>0&&S.push({label:"cancel rejects (35=9)",value:y.cancel_rejects.toLocaleString()}),(y.cancels_canceled??0)>0&&S.push({label:"canceled (150=4)",value:y.cancels_canceled.toLocaleString()}),(y.cancels_lost??0)>0&&S.push({label:"cancels lost",value:y.cancels_lost.toLocaleString()}))),g&&(S.push({label:"fills/s",value:Math.round(g.fps_live).toLocaleString()}),(j==null?void 0:j.fills_per_window)!=null&&(j==null?void 0:j.fill_window_ms)!=null&&S.push({label:"fill rate (cfg)",value:`${j.fills_per_window.toLocaleString()} / ${j.fill_window_ms}ms`}),S.push({label:"fills",value:g.fills_sent.toLocaleString()},{label:"fills/order",value:tj(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&&S.push({label:"amends recv",value:g.amends_received.toLocaleString()}),g.amends_uncorrelated>0&&S.push({label:"amends no-corr",value:g.amends_uncorrelated.toLocaleString()}),g.amend_waits_expired>0&&S.push({label:"amend waits expired",value:g.amend_waits_expired.toLocaleString()}),(g.cancels_received??0)>0&&S.push({label:"cancels recv",value:g.cancels_received.toLocaleString()}),(g.cancels_uncorrelated??0)>0&&S.push({label:"cancels no-corr",value:g.cancels_uncorrelated.toLocaleString()})),b&&(S.push({label:"relay ↓/s",value:Math.round(b.mdps_live).toLocaleString()},{label:"relay ↑/s",value:Math.round(b.mups_live).toLocaleString()},{label:"relayed ↓",value:b.msgs_down.toLocaleString()},{label:"relayed ↑",value:b.msgs_up.toLocaleString()}),b.dropped>0&&S.push({label:"relay dropped",value:b.dropped.toLocaleString()})),x&&(S.push({label:"lost",value:(x.lost_timeout+(x.lost_shutdown??0)).toLocaleString()},{label:"rejected",value:x.rejected.toLocaleString()}),(x.lost_shutdown??0)>0&&S.push({label:"lost (cut off)",value:(x.lost_shutdown??0).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 ${Gs}`,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:()=>Z.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:()=>Z.perf.runs.export(e,"latency"),children:"Latency CSV"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Z.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((M,$)=>l.jsxs("div",{className:"perf-warning-banner",children:["⚠ ",M]},$)),!t&&!d&&l.jsx("div",{className:"loading-msg",children:"Waiting for first snapshot…"}),S.length>0&&l.jsx("div",{className:"stat-cards",children:S.map(({label:M,value:$})=>l.jsxs("div",{className:"stat-card",children:[l.jsx("span",{className:"stat-card-value",children:$}),l.jsx("span",{className:"stat-card-label",children:M})]},M))}),t&&l.jsxs("div",{className:"perf-lat-row",children:[l.jsx(kn,{title:"Response latency",sub:"order-2-ack",stats:t.response_latency}),l.jsx(kn,{title:"Fill-completion latency",sub:"order-2-last-fill",stats:t.fill_completion_latency}),((O=t.amend_latency)==null?void 0:O.count)>0&&l.jsx(kn,{title:"Amend latency",sub:"amend-2-replaced",stats:t.amend_latency}),((P=t.cancel_latency)==null?void 0:P.count)>0&&l.jsx(kn,{title:"Cancel latency",sub:"cancel-2-reply",stats:t.cancel_latency}),(((k=(E=t.relay)==null?void 0:E.down_latency)==null?void 0:k.count)??0)>0&&l.jsx(kn,{title:"Relay pass-through ↓",sub:"client→venue hop",stats:t.relay.down_latency}),(((C=(T=t.relay)==null?void 0:T.up_latency)==null?void 0:C.count)??0)>0&&l.jsx(kn,{title:"Relay pass-through ↑",sub:"venue→client hop",stats:t.relay.up_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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),l.jsx(Me,{type:"monotone",dataKey:"ops",name:"orders/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"fps",name:"fills/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.amends_sent)??0)>0&&l.jsx(Me,{type:"monotone",dataKey:"aps",name:"amends/s",stroke:"#c084fc",dot:!1,isAnimationActive:!1}),((y==null?void 0:y.cancels_sent)??0)>0&&l.jsx(Me,{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(Me,{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(Me,{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:Gs,value:c,onChange:M=>u(M.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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),c==="direction"?l.jsxs(l.Fragment,{children:[l.jsx(Me,{type:"monotone",dataKey:"tps",name:"total msgs/s",stroke:"#fbbf24",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"mco",name:"sent/s (requests)",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"mci",name:"received/s (replies)",stroke:"#a78bfa",dot:!1,isAnimationActive:!1})]}):l.jsxs(l.Fragment,{children:[l.jsx(Me,{type:"monotone",dataKey:M=>M.mco+M.mci,name:"client session/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:M=>M.mvo+M.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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),l.jsx(Me,{type:"monotone",dataKey:"p50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Me,{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(Kr,{width:"100%",height:200,children:l.jsxs(Pa,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Cr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Er,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:M=>`${Math.round(M)}s`}),l.jsx(Ar,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(Jr,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:M=>`${Math.round(Number(M))}s`}),l.jsx(Mn,{wrapperStyle:{fontSize:11}}),l.jsx(Me,{type:"monotone",dataKey:"fc50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(Me,{type:"monotone",dataKey:"fc99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})})]}),d&&l.jsx(kW,{runId:e}),d&&(a==null?void 0:a.summary)&&l.jsx(IW,{status:a})]})}function kW({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),Z.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 ${Gs}`,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 IW({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)",tj(t.fills_sent,t.completions)],["Fill ratio (completed)",t.fill_ratio.toFixed(3)],["Lost / dropped / rejected",`${t.lost_timeout+(t.lost_shutdown??0)} / ${t.dropped} / ${t.rejected}`],...(t.lost_shutdown??0)>0?[["Lost breakdown (timeout / cut off at end)",`${t.lost_timeout} / ${t.lost_shutdown}`]]:[],["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 TW=new Set(["completed","stopped","error"]);function MW(e){if(!e)return"—";try{return new Date(e*1e3).toLocaleTimeString()}catch{return"—"}}function DW({activeRunId:e,onSelect:t,refreshKey:r}){const[n,a]=h.useState([]);h.useEffect(()=>{let s=!0;const o=()=>Z.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 Z.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:MW(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:!TW.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 Wl=200,Rn=(e,t,r=120)=>({field:e,headerName:t,width:r,type:"numericColumn",valueFormatter:n=>n.value==null?"":Number(n.value).toLocaleString()}),Gc=(e,t)=>({field:e,headerName:t,width:150,type:"numericColumn",valueFormatter:r=>r.value==null?"":`${Math.round(Number(r.value)).toLocaleString()}`}),RW=[{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},Rn("qty","Qty",90),Rn("price","Price",100),Gc("response_latency_us","Resp µs"),Gc("fill_latency_us","Fill µs"),Rn("fill_qty","Fill qty",100),Rn("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}],$W=[{field:"scenario_name",headerName:"Scenario",flex:1,minWidth:160},Gc("latency_us","Latency µs"),Rn("msg_count","Msgs",90),Rn("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 LW({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"?Z.perf.runs.messages:Z.perf.runs.scenarios)(e,{limit:Wl,offset:a*Wl}).then(x=>{g&&(o(x.items),u(x.total))}).catch(()=>{g&&(o([]),u(0))}).finally(()=>{g&&d(!1)}),()=>{g=!1}},[e,r,a]);const m=h.useMemo(()=>r==="messages"?RW:$W,[r]),p=h.useMemo(()=>({sortable:!0,filter:!0,resizable:!0}),[]),v=Math.max(0,Math.ceil(c/Wl)-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 FW(){const{theme:e,toggleTheme:t,branding:r,setSessions:n}=X(),a=al(),i=r!=null&&r.prefix?`${r.prefix} FIXture`:"FIXture";Uv(),h.useEffect(()=>{Z.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(an,{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(AW,{runId:c},c)}),s==="results"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(LW,{runId:c},c)}),s==="history"&&l.jsx("div",{className:"perf-pane",children:l.jsx(DW,{activeRunId:c,onSelect:p,refreshKey:f})})]})]})}function BW(){return X(t=>t.token)?l.jsx(kv,{}):l.jsx(Ki,{to:"/login",replace:!0})}function zW(){const e=X(t=>t.user);return e?e.role!=="platform_admin"?l.jsx(Ki,{to:"/",replace:!0}):l.jsx(kv,{}):l.jsx(Ki,{to:"/login",replace:!0})}function WW(){const e=X(t=>t.setBranding);h.useEffect(()=>{fetch("/api/branding").then(t=>t.json()).then(t=>{e(t),Qw(t)}).catch(()=>{})},[])}function UW(){const e=pn(),t=X(r=>r.theme);return WW(),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(d_,{children:[l.jsx(zt,{path:"/setup",element:l.jsx(vW,{})}),l.jsx(zt,{path:"/login",element:l.jsx(pW,{})}),l.jsx(zt,{path:"/register",element:l.jsx(gW,{})}),l.jsxs(zt,{element:l.jsx(BW,{}),children:[l.jsx(zt,{path:"/",element:l.jsx(mW,{})}),l.jsxs(zt,{element:l.jsx(zW,{}),children:[l.jsx(zt,{path:"/admin/users",element:l.jsx(jW,{})}),l.jsx(zt,{path:"/perf",element:l.jsx(FW,{})})]})]}),l.jsx(zt,{path:"*",element:l.jsx(Ki,{to:"/",replace:!0})})]})}Ul.createRoot(document.getElementById("root")).render(l.jsx(pv.StrictMode,{children:l.jsx(D_,{children:l.jsx(UW,{})})}));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>FIXture</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-C2JvCG6w.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/ag-grid-_QKprVdm.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-C9bcm_lN.css">
|
|
11
11
|
</head>
|
|
@@ -64,8 +64,8 @@ fixture/fix_specs/FIX44.xml
|
|
|
64
64
|
fixture/static/favicon.svg
|
|
65
65
|
fixture/static/index.html
|
|
66
66
|
fixture/static/assets/ag-grid-_QKprVdm.js
|
|
67
|
+
fixture/static/assets/index-C2JvCG6w.js
|
|
67
68
|
fixture/static/assets/index-C9bcm_lN.css
|
|
68
|
-
fixture/static/assets/index-COvq8e1-.js
|
|
69
69
|
fixture/static/assets/index-CyNOPa0n.js
|
|
70
70
|
fixture/static/assets/react-vendor-2eF0YfZT.js
|
|
71
71
|
fixture/ui/__init__.py
|
|
@@ -432,6 +432,25 @@ def test_no_ceiling_bucket_by_default():
|
|
|
432
432
|
assert _run()._ceiling_bucket is None
|
|
433
433
|
|
|
434
434
|
|
|
435
|
+
async def test_shutdown_losses_counted_and_reconcile():
|
|
436
|
+
# fill_ratio=0 → orders are acked but never filled, so none complete; they're
|
|
437
|
+
# still pending at shutdown → counted as lost_shutdown (not lost_timeout, which
|
|
438
|
+
# the sweeper can't reach with a long order timeout). The summary must reconcile.
|
|
439
|
+
sm = FakeSM("c", "v")
|
|
440
|
+
cfg = _cfg(rate=RateConfig(orders_per_window=20, order_window_ms=100,
|
|
441
|
+
fills_per_window=20, fill_window_ms=100, fill_ratio=0.0),
|
|
442
|
+
test=RunTestConfig(duration=1, max_orders=10, scenario_timeout_ms=60000))
|
|
443
|
+
run = PerfRun("lostrun1", cfg, sm, FakeWriter())
|
|
444
|
+
run.start()
|
|
445
|
+
await asyncio.wait_for(run._main, timeout=20)
|
|
446
|
+
assert run.stats.completions == 0
|
|
447
|
+
assert run.stats.lost_shutdown > 0
|
|
448
|
+
assert run.stats.lost_timeout == 0
|
|
449
|
+
# every sent order is accounted for exactly once
|
|
450
|
+
assert run.stats.orders_sent == (run.stats.completions
|
|
451
|
+
+ run.stats.lost_timeout + run.stats.lost_shutdown)
|
|
452
|
+
|
|
453
|
+
|
|
435
454
|
async def test_middle_manager_relays_end_to_end():
|
|
436
455
|
# client → relay_in → relay_out → venue, and the fills back. Orders complete
|
|
437
456
|
# through the relay, and the relay times its own pass-through both ways.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|