fixtureqa 0.4.9__tar.gz → 0.4.10__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.9/fixtureqa.egg-info → fixtureqa-0.4.10}/PKG-INFO +1 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/perf_engine.py +35 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/perf_models.py +2 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/perf_stats.py +4 -0
- fixtureqa-0.4.9/fixture/static/assets/index-DrmyYeG0.js → fixtureqa-0.4.10/fixture/static/assets/index-BFPDQdQb.js +1 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/static/index.html +1 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixtureqa.egg-info/SOURCES.txt +1 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/pyproject.toml +1 -1
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_perf_engine.py +54 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/LICENSE +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/README.md +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/__init__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/__main__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/app.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/deps.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/auth.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/events.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/exec_csv_writer.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/models.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/perf_payload.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/session.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/server.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/static/assets/index-BwQf-cei.css +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/setup.cfg +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_auth.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_config_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_health.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_inbound.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_message_store.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_sessions.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_templates.py +0 -0
- {fixtureqa-0.4.9 → fixtureqa-0.4.10}/tests/test_ws.py +0 -0
|
@@ -439,6 +439,15 @@ class PerfRun:
|
|
|
439
439
|
cfg = self.config
|
|
440
440
|
self.stats.amends_received += 1
|
|
441
441
|
if of.get(cfg.correlation_tag) is None:
|
|
442
|
+
# Can't reply — there is nothing to echo for the client to correlate.
|
|
443
|
+
# Classic cause: the OMS rebuilds the 35=G downstream and drops the
|
|
444
|
+
# custom tag it passes through fine on NewOrderSingle.
|
|
445
|
+
self.stats.amends_uncorrelated += 1
|
|
446
|
+
if self.stats.amends_uncorrelated == 1:
|
|
447
|
+
self.warnings.append(
|
|
448
|
+
f"venue received 35=G without correlation tag "
|
|
449
|
+
f"{cfg.correlation_tag} — no replace ack sent; check the "
|
|
450
|
+
f"counterparty passes the tag through on amends")
|
|
442
451
|
return
|
|
443
452
|
order_qty = _to_int(of.get(T_ORDERQTY), 0)
|
|
444
453
|
px = of.get(T_PRICE) or "0"
|
|
@@ -540,10 +549,33 @@ class PerfRun:
|
|
|
540
549
|
sub = self._client_sub
|
|
541
550
|
while True:
|
|
542
551
|
inb = await sub.get()
|
|
552
|
+
if inb.msg_type == "9":
|
|
553
|
+
self._on_cancel_reject(inb.fields)
|
|
554
|
+
continue
|
|
543
555
|
if inb.msg_type != "8":
|
|
544
556
|
continue
|
|
545
557
|
self._on_exec_report(inb.fields, inb.recv_perf_ns or time.perf_counter_ns())
|
|
546
558
|
|
|
559
|
+
def _on_cancel_reject(self, f: dict) -> None:
|
|
560
|
+
"""OrderCancelReject (35=9): the counterparty refused the amend. Count
|
|
561
|
+
it (these would otherwise be invisible — only 35=8 is correlated) and
|
|
562
|
+
release the in-flight amend so the order finalizes normally instead of
|
|
563
|
+
being reaped as amends_lost."""
|
|
564
|
+
self.stats.amend_rejects += 1
|
|
565
|
+
if self.stats.amend_rejects == 1:
|
|
566
|
+
self.warnings.append(
|
|
567
|
+
"counterparty rejected an amend (35=9 OrderCancelReject) — "
|
|
568
|
+
"it may not match OrigClOrdID(41) against its own order chain")
|
|
569
|
+
corr = f.get(self.config.correlation_tag)
|
|
570
|
+
entry = self._pending.get(corr) if corr is not None else None
|
|
571
|
+
if entry is None or not entry.get("amend_inflight"):
|
|
572
|
+
return
|
|
573
|
+
entry["amend_inflight"] = False
|
|
574
|
+
entry["pending_clordid"] = None # chain tip stays at the last accepted
|
|
575
|
+
if entry.get("completed"):
|
|
576
|
+
self._pending.pop(corr, None)
|
|
577
|
+
self._finalize(entry, "filled")
|
|
578
|
+
|
|
547
579
|
def _on_exec_report(self, f: dict, now_ns: int) -> None:
|
|
548
580
|
cfg = self.config
|
|
549
581
|
corr = f.get(cfg.correlation_tag)
|
|
@@ -722,6 +754,7 @@ class PerfRun:
|
|
|
722
754
|
amends_sent=self.stats.amends_sent,
|
|
723
755
|
replaces_received=self.stats.replaces_received,
|
|
724
756
|
amends_lost=self.stats.amends_lost,
|
|
757
|
+
amend_rejects=self.stats.amend_rejects,
|
|
725
758
|
aps_live=round(aps, 1),
|
|
726
759
|
scenarios_dispatched=self.stats.scenarios_dispatched,
|
|
727
760
|
scenarios_per_sec=round(sps, 1),
|
|
@@ -735,7 +768,8 @@ class PerfRun:
|
|
|
735
768
|
fills_sent=self.stats.fills_sent, fps_live=round(fps, 1),
|
|
736
769
|
fill_ratio=round(self.stats.fill_ratio(), 3),
|
|
737
770
|
unfilled=max(0, self.stats.orders_sent - self.stats.completions),
|
|
738
|
-
amends_received=self.stats.amends_received
|
|
771
|
+
amends_received=self.stats.amends_received,
|
|
772
|
+
amends_uncorrelated=self.stats.amends_uncorrelated)
|
|
739
773
|
snap = LiveSnapshot(
|
|
740
774
|
run_id=self.run_id, status=self.status, mode=self.config.mode,
|
|
741
775
|
elapsed_s=round(self._elapsed_s(), 1), duration_s=self.config.test.duration,
|
|
@@ -199,6 +199,7 @@ class ClientLeg(BaseModel):
|
|
|
199
199
|
amends_sent: int = 0
|
|
200
200
|
replaces_received: int = 0
|
|
201
201
|
amends_lost: int = 0
|
|
202
|
+
amend_rejects: int = 0
|
|
202
203
|
aps_live: float = 0.0
|
|
203
204
|
scenarios_dispatched: int = 0
|
|
204
205
|
scenarios_per_sec: float = 0.0
|
|
@@ -215,6 +216,7 @@ class VenueLeg(BaseModel):
|
|
|
215
216
|
unfilled: int = 0
|
|
216
217
|
venue_proc_us_p50: float = 0.0
|
|
217
218
|
amends_received: int = 0
|
|
219
|
+
amends_uncorrelated: int = 0
|
|
218
220
|
|
|
219
221
|
|
|
220
222
|
class SnapshotErrors(BaseModel):
|
|
@@ -122,7 +122,9 @@ class PerfStats:
|
|
|
122
122
|
self.amends_sent = 0 # client sent a 35=G
|
|
123
123
|
self.replaces_received = 0 # client got a Replaced ack (150=5)
|
|
124
124
|
self.amends_lost = 0 # amend in flight when its order was reaped
|
|
125
|
+
self.amend_rejects = 0 # client got an OrderCancelReject (35=9)
|
|
125
126
|
self.amends_received = 0 # venue saw an inbound 35=G
|
|
127
|
+
self.amends_uncorrelated = 0 # inbound 35=G lacked the correlation tag (no reply)
|
|
126
128
|
self.lost_timeout = 0
|
|
127
129
|
self.dropped = 0
|
|
128
130
|
self.rejected = 0
|
|
@@ -179,7 +181,9 @@ class PerfStats:
|
|
|
179
181
|
"amends_sent": self.amends_sent,
|
|
180
182
|
"replaces_received": self.replaces_received,
|
|
181
183
|
"amends_lost": self.amends_lost,
|
|
184
|
+
"amend_rejects": self.amend_rejects,
|
|
182
185
|
"amends_received": self.amends_received,
|
|
186
|
+
"amends_uncorrelated": self.amends_uncorrelated,
|
|
183
187
|
"lost_timeout": self.lost_timeout,
|
|
184
188
|
"dropped": self.dropped,
|
|
185
189
|
"rejected": self.rejected,
|
|
@@ -99,4 +99,4 @@ Please change the parent <Route path="${b}"> to <Route path="${b==="/"?"*":`${b}
|
|
|
99
99
|
]
|
|
100
100
|
}
|
|
101
101
|
]
|
|
102
|
-
}`;function qz({onClose:e}){const[t,r]=h.useState(""),[n,a]=h.useState(""),[i,o]=h.useState(!1);h.useEffect(()=>{Z.specOverlay.get().then(c=>r(JSON.stringify(c,null,2))).catch(console.error)},[]);async function s(){a("");let c;try{c=JSON.parse(t)}catch{a("Invalid JSON");return}o(!0);try{await Z.specOverlay.set(c),e()}catch(u){a(u instanceof Error?u.message:"Save failed")}finally{o(!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:Kz})}),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:s,disabled:i,children:i?"Saving…":"Save overlay"})]})]})})}const Mw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgCgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGNpcmNsZSBjeD0iMjA3IiBjeT0iNzgiIHI9IjEwIiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjY1IiB4Mj0iMjQ4IiB5Mj0iNjUiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDAsIDE4MCwgMjAwKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMCwgMTgwLCAyMDApO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNztmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI5MSIgeDI9IjI0NCIgeTI9IjkxIiBzdHJva2U9IiMwMGI0YzgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjQ1IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigwLCAxODAsIDIwMCk7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC40NTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgoKICA8dGV4dCB4PSIyNzIiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtbW9ubykiIGZvbnQtc2l6ZT0iNjYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6NTBweDtmb250LXdlaWdodDozMDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byI+dHVyZTwvdGV4dD4KPC9zdmc+",Dw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSIxMCIgZmlsbD0iIzFhM2E1YyIgc3R5bGU9ImZpbGw6cmdiKDI2LCA1OCwgOTIpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI2NSIgeDI9IjI0OCIgeTI9IjY1IiBzdHJva2U9IiMxYTNhNWMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigyNiwgNTgsIDkyKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMjYsIDU4LCA5Mik7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC43O2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjkxIiB4Mj0iMjQ0IiB5Mj0iOTEiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNDUiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDI2LCA1OCwgOTIpO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNDU7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJnF1b3Q7U2Vnb2UgVUkmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7Zm9udC13ZWlnaHQ6NDAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iLz4KCiAgPHRleHQgeD0iMjcyIiB5PSIxMDUiIGZvbnQtZmFtaWx5PSJ2YXIoLS1mb250LW1vbm8pIiBmb250LXNpemU9IjY2IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMWEzYTVjIiBsZXR0ZXItc3BhY2luZz0iLTEiIHN0eWxlPSJmaWxsOnJnYigyNiwgNTgsIDkyKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjUwcHg7Zm9udC13ZWlnaHQ6MzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPnR1cmU8L3RleHQ+Cjwvc3ZnPg==";function Gs(){const e=V(r=>r.theme),t=V(r=>r.branding);return e==="dark"?(t==null?void 0:t.logo_dark)||Mw:(t==null?void 0:t.logo_light)||Dw}function Yz(){const e=V(g=>g.setSessions),t=V(g=>g.theme),r=V(g=>g.toggleTheme),n=V(g=>g.user),a=V(g=>g.logout),i=V(g=>g.openTabs),o=V(g=>g.activeTabId),s=V(g=>g.branding),c=Gs(),u=s!=null&&s.prefix?`${s.prefix} FIXture`:"FIXture",[f,d]=h.useState(!1),[m,p]=h.useState(!1),[v,y]=h.useState(!1);return Pv(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{Z.sessions.list().then(e).catch(console.error)},[]),h.useEffect(()=>{if(!n){a_();return}Z.customTags.list().then(Ov).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(Yr,{to:"/perf",className:"btn btn-sm btn-ghost",title:"Performance testing",children:"Perf"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(Yr,{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(y_,{}),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(kz,{}),l.jsx("div",{className:"tab-panels",children:i.map(g=>l.jsx("div",{className:`tab-panel${g===o?" active":""}`,children:l.jsx(Ez,{sessionId:g})},g))})]})})]}),f&&l.jsx(zz,{onClose:()=>d(!1)}),m&&l.jsx(Zz,{onClose:()=>p(!1)}),v&&l.jsx(qz,{onClose:()=>y(!1)})]})}function Xz(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),[c,u]=h.useState(!1),{login:f}=V(),d=Gs(),m=on();h.useEffect(()=>{fetch("/api/auth/register/status").then(v=>v.json()).then(v=>u(v.enabled)).catch(()=>{})},[]);async function p(v){v.preventDefault(),i(""),s(!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{s(!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:o,children:o?"Signing in…":"Sign in"})]}),c&&l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["No account? ",l.jsx(Yr,{to:"/register",children:"Register"})]})]})})}function Vz(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),c=Gs(),u=on();async function f(d){d.preventDefault(),i(""),s(!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{s(!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:o,children:o?"Creating…":"Create admin"})]})]})})}function Jz(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),[c,u]=h.useState(null),{login:f,theme:d}=V(),m=on();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(""),s(!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{s(!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"?Mw:Dw,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:o,children:o?"Creating account…":"Create account"})]}),l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["Already have an account? ",l.jsx(Yr,{to:"/login",children:"Sign in"})]})]})})}const Qz=["accent","bg","bg2","bg3","border","text","text2"];function Rw(e){let t=document.getElementById("branding-style");t||(t=document.createElement("style"),t.id="branding-style",document.head.appendChild(t));const r=Qz.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 Ol(e){return new Promise((t,r)=>{const n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}const $w=["user","user_admin","platform_admin"];function eW(e){return new Date(e*1e3).toLocaleString()}function tW({onClose:e,onCreated:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=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:o});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:o,onChange:p=>s(p.target.value),children:$w.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 rW({user:e,onClose:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),[c,u]=h.useState(!1);async function f(d){d.preventDefault(),i(""),s(!0);try{await Z.admin.users.update(e.uid,{password:r}),u(!0)}catch(m){i(m.message)}finally{s(!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:o,children:o?"Saving…":"Save"})]})]})]})})}function nW(){const e=V(k=>k.user),t=on(),[r,n]=h.useState([]),[a,i]=h.useState(null),[o,s]=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}=V(),[b,x]=h.useState(!1),[w,_]=h.useState(null),[S,j]=h.useState("");async function O(){try{const[k,$,W,G]=await Promise.all([Z.admin.users.list(),Z.admin.settings.get(),Z.admin.housekeeping.get(),Z.admin.branding.get()]);n(k),i($),s(W),p(G)}catch(k){j(k.message)}}h.useEffect(()=>{O()},[]);async function N(k){try{const $=await Z.admin.users.update(k.uid,{is_active:!k.is_active});n(W=>W.map(G=>G.uid===k.uid?$:G))}catch($){j($.message)}}async function A(k,$){try{const W=await Z.admin.users.update(k.uid,{role:$});n(G=>G.map(J=>J.uid===k.uid?W:J))}catch(W){j(W.message)}}async function I(k){if(confirm(`Delete user "${k.username}"? This cannot be undone.`))try{await Z.admin.users.delete(k.uid),n($=>$.filter(W=>W.uid!==k.uid))}catch($){j($.message)}}async function T(){if(a)try{const k=await Z.admin.settings.update({registration_enabled:!a.enabled});i(k)}catch(k){j(k.message)}}async function C(k){const $=parseInt(k,10);if(!(isNaN($)||$<1))try{const W=await Z.admin.settings.update({max_users:$});i(W)}catch(W){j(W.message)}}async function U(){if(o)try{const k=await Z.admin.housekeeping.update({enabled:!o.enabled});s(k)}catch(k){j(k.message)}}async function L(k,$){const W=parseInt($,10);if(!(isNaN(W)||W<0))try{const G=await Z.admin.housekeeping.update({[k]:W});s(G)}catch(G){j(G.message)}}async function H(){d(!0),u(null);try{const k=await Z.admin.housekeeping.runNow();u(`Done — deleted ${k.msgs_deleted} messages, ${k.logs_deleted} log files`)}catch(k){j(k.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:k=>C(k.target.value)}),l.jsxs("span",{className:"admin-settings-label",style:{color:"var(--text2)"},children:["(",a.current_users," / ",a.max_users," registered)"]})]}),o&&l.jsxs("div",{className:"admin-settings-bar",children:[l.jsx("span",{className:"admin-settings-label",children:"Housekeeping:"}),l.jsx("button",{className:`btn btn-sm ${o.enabled?"btn-stop":"btn-start"}`,onClick:U,children:o.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:o.msg_retention_days,min:0,title:"0 = keep forever",onBlur:k=>L("msg_retention_days",k.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:o.log_retention_days,min:0,title:"0 = keep forever",onBlur:k=>L("log_retention_days",k.target.value)}),l.jsx("button",{className:"btn btn-sm btn-ghost",style:{marginLeft:16},onClick:H,disabled:f,children:f?"Running…":"Run Now"}),c&&l.jsx("span",{style:{marginLeft:8,color:"var(--green)",fontSize:12},children:c})]}),m&&l.jsxs("div",{className:"admin-settings-bar",style:{flexDirection:"column",alignItems:"flex-start",gap:10},children:[l.jsx("span",{className:"admin-settings-label",style:{fontWeight:700},children:"Branding"}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Brand prefix:"}),l.jsx("input",{className:"admin-max-users-input",style:{width:160},value:m.prefix,placeholder:"e.g. Acme (shown as Acme FIXture)",onChange:k=>p({...m,prefix:k.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:k=>p({...m,accent:k.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(([k,$,W])=>l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[l.jsxs("span",{className:"admin-settings-label",children:[$,":"]}),l.jsx("input",{type:"color",value:m[k]||W,onChange:G=>p({...m,[k]:G.target.value}),title:$}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,[k]:""}),title:"Reset to default",children:"↺"})]},k))}),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 k=>{var W;const $=(W=k.target.files)==null?void 0:W[0];$&&p({...m,logo_dark:await Ol($)})}}),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 k=>{var W;const $=(W=k.target.files)==null?void 0:W[0];$&&p({...m,logo_light:await Ol($)})}}),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 k=>{var W;const $=(W=k.target.files)==null?void 0:W[0];$&&p({...m,favicon:await Ol($)})}}),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 k=await Z.admin.branding.update(m);p(k),g(k),Rw(k)}catch(k){j(k.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(k=>l.jsxs("tr",{className:k.is_active?"":"admin-row-inactive",children:[l.jsxs("td",{children:[k.username,k.uid===(e==null?void 0:e.uid)&&l.jsx("span",{className:"admin-you-badge",children:" (you)"})]}),l.jsx("td",{children:l.jsx("select",{value:k.role,onChange:$=>A(k,$.target.value),className:"admin-role-select",disabled:k.uid===(e==null?void 0:e.uid),children:$w.map($=>l.jsx("option",{value:$,children:$},$))})}),l.jsx("td",{children:l.jsx("span",{style:{color:k.is_active?"var(--green)":"var(--text2)"},children:k.is_active?"Active":"Inactive"})}),l.jsx("td",{style:{color:"var(--text2)",fontSize:11},children:eW(k.created_at)}),l.jsx("td",{children:l.jsxs("div",{className:"admin-row-actions",children:[l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>N(k),disabled:k.uid===(e==null?void 0:e.uid),title:k.is_active?"Deactivate":"Activate",children:k.is_active?"Deactivate":"Activate"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>_(k),title:"Reset password",children:"Reset PW"}),l.jsx("button",{className:"btn btn-sm btn-danger",onClick:()=>I(k),disabled:k.uid===(e==null?void 0:e.uid),title:"Delete user",children:"✕"})]})})]},k.uid))})]}),b&&l.jsx(tW,{onClose:()=>x(!1),onCreated:k=>n($=>[...$,k])}),w&&l.jsx(rW,{user:w,onClose:()=>_(null)})]})}const Ec={enabled:!1,every_n_fills:1,amend_ratio:1,max_per_order:1,price_offset:.01,pending_replace:!1},Kp={name:"smoke",mode:"loopback",client_session_id:null,venue_session_id:null,correlation_tag:376,exec_id_tag:25116,rate:{orders_per_window:50,order_window_ms:100,dispatch:"smooth",fill_dispatch:"smooth",fills_per_window:50,fill_window_ms:100,fill_ratio:1,allow_burst:!1,max_burst_multiplier:2},test:{duration:30,max_orders:0,scenario_timeout_ms:3e4,max_pending:1e5,on_saturation:"pause",output:"results",record_execs:!1},amend:Ec,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:{}}},aW=["loopback","manager","client","venue"];function qp(e,t){return t?Math.round(e/t*1e3):0}function iW({onStarted:e}){var ea,D;const t=V(P=>P.sessions),[r,n]=h.useState(Kp),[a,i]=h.useState(Kp.payload.order.symbols.join(", ")),[o,s]=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==="loopback"||r.mode==="manager"||r.mode==="client",_=r.mode==="loopback"||r.mode==="manager"||r.mode==="venue",S=r.amend??Ec,j=r.client_session_id?t[r.client_session_id]:void 0,O=r.venue_session_id?t[r.venue_session_id]:void 0,N=!w||(j==null?void 0:j.status)==="LOGGED_ON",A=!_||(O==null?void 0:O.status)==="LOGGED_ON",I=(!w||!!r.client_session_id)&&(!_||!!r.venue_session_id),T=(()=>{if(!r.name.trim())return"Name is required";const P=r.rate;if(P.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(w){if(P.orders_per_window<1||P.order_window_ms<1)return"Order rate must be > 0";if(a.split(",").map(it=>it.trim()).filter(Boolean).length===0)return"At least one symbol is required";const B=r.payload.order;if(B.quantity_min<1||B.quantity_max<1||B.quantity_min>B.quantity_max)return"Check quantity min/max";if(B.price_min<=0||B.price_max<=0||B.price_min>B.price_max)return"Check price min/max";if(S.enabled){if(S.every_n_fills<1)return"Amend every-n-fills must be ≥ 1";if(S.amend_ratio<0||S.amend_ratio>1)return"Amend ratio must be between 0 and 1";if(S.max_per_order<0)return"Max amends per order must be ≥ 0";if(S.price_offset<=0)return"Amend price offset must be > 0"}}if(_){if(P.fills_per_window<1||P.fill_window_ms<1)return"Fill rate must be > 0";if(r.payload.fill.fills_per_order<1)return"Fills per order must be ≥ 1";if(P.fill_ratio<0||P.fill_ratio>1)return"Fill ratio must be between 0 and 1"}return null})(),C=I&&N&&A&&!T&&!f;async function U(){try{p(await Z.perf.configs.list())}catch{}}h.useEffect(()=>{U()},[]),h.useEffect(()=>{Z.templates.list().then(b).catch(()=>{})},[]);const L=g.filter(P=>P.msg_type==="D"),H=g.filter(P=>P.msg_type==="8"),k=!!r.payload.order_template_id,$=!!r.payload.exec_template_id,W=((ea=L.find(P=>P.id===r.payload.order_template_id))==null?void 0:ea.name)??"selected",G=((D=H.find(P=>P.id===r.payload.exec_template_id))==null?void 0:D.name)??"selected",J=P=>n(B=>({...B,...P})),ae=P=>n(B=>({...B,rate:{...B.rate,...P}})),ee=P=>n(B=>({...B,test:{...B.test,...P}})),me=P=>n(B=>({...B,amend:{...Ec,...B.amend,...P}})),de=P=>n(B=>({...B,payload:{...B.payload,order:{...B.payload.order,...P}}})),Oe=P=>n(B=>({...B,payload:{...B.payload,fill:{...B.payload.fill,...P}}})),R=P=>n(B=>({...B,payload:{...B.payload,...P}})),[ie,ne]=h.useState([]),z=P=>{ne(P),R({gen_rules:Object.fromEntries(P.filter(B=>B.tag.trim()).map(B=>[B.tag.trim(),B.value]))})},F=(P,B)=>z(ie.map((it,Er)=>Er===P?{...it,...B}:it));function Q(){const P=a.split(",").map(B=>B.trim()).filter(Boolean);return{...r,client_session_id:w?r.client_session_id:null,venue_session_id:_?r.venue_session_id:null,payload:{...r.payload,order:{...r.payload.order,symbols:P}}}}async function M(){u(""),d(!0);try{const{run_id:P}=await Z.perf.runs.create(Q());e(P)}catch(P){u(P.message??"Failed to start run")}finally{d(!1)}}async function Ie(){u("");try{await Z.perf.configs.save(Q()),await U()}catch(P){u(P.message??"Failed to save config")}}async function se(P){if(y(P),!!P){u("");try{const B=await Z.perf.configs.get(P);n(B),i(B.payload.order.symbols.join(", ")),ne(Object.entries(B.payload.gen_rules??{}).map(([it,Er])=>({tag:it,value:Er})))}catch(B){u(B.message??"Failed to load config")}}}async function ze(){if(v&&window.confirm("Delete this saved config?"))try{await Z.perf.configs.delete(v),y(""),await U()}catch(P){u(P.message??"Failed to delete config")}}const Te=P=>{const B=t[P];return B?`${B.display_name||`${B.sender_comp_id} → ${B.target_comp_id}`} (${B.status})`:P},_t=x.filter(P=>P.session_role!=="venue"),mn=x.filter(P=>P.session_role!=="client");return l.jsxs("div",{className:"perf-form",children:[c&&l.jsx("div",{className:"error-msg",children:c}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Saved configs"}),l.jsxs("div",{className:"perf-field-row",children:[l.jsxs("select",{value:v,onChange:P=>se(P.target.value),children:[l.jsx("option",{value:"",children:"(load a saved config…)"}),m.map(P=>l.jsxs("option",{value:P.config_id,children:[P.name," — ",P.mode]},P.config_id))]}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:Ie,children:"Save current"}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:ze,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:P=>J({name:P.target.value})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Mode"}),l.jsx("select",{value:r.mode,onChange:P=>J({mode:P.target.value}),children:aW.map(P=>l.jsx("option",{value:P,children:P},P))})]}),w&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Client session"}),l.jsxs("select",{value:r.client_session_id??"",onChange:P=>J({client_session_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),_t.map(P=>l.jsx("option",{value:P.session_id,children:Te(P.session_id)},P.session_id))]}),j&&l.jsx(Yp,{status:j.status})]}),_&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue session"}),l.jsxs("select",{value:r.venue_session_id??"",onChange:P=>J({venue_session_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),mn.map(P=>l.jsx("option",{value:P.session_id,children:Te(P.session_id)},P.session_id))]}),O&&l.jsx(Yp,{status:O.status})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Rate"}),l.jsxs("div",{className:"perf-field-grid",children:[w&&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:P=>ae({orders_per_window:Number(P.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:P=>ae({order_window_ms:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order dispatch"}),l.jsxs("select",{value:r.rate.dispatch??"smooth",onChange:P=>ae({dispatch:P.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(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:P=>ae({fills_per_window:Number(P.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:P=>ae({fill_window_ms:Number(P.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:P=>ae({fill_ratio:Number(P.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:P=>ae({fill_dispatch:P.target.value}),children:[l.jsx("option",{value:"smooth",children:"smooth (evenly spaced)"}),l.jsx("option",{value:"burst",children:"burst (all at window start)"})]})]})]})]}),l.jsxs("div",{className:"perf-hint",children:[w&&l.jsxs(l.Fragment,{children:["≈ ",qp(r.rate.orders_per_window,r.rate.order_window_ms).toLocaleString()," orders/s"]}),w&&_&&" · ",_&&l.jsxs(l.Fragment,{children:["≈ ",qp(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:P=>ee({duration:Number(P.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:P=>ee({max_orders:Number(P.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:P=>ee({scenario_timeout_ms:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"On saturation"}),l.jsxs("select",{value:r.test.on_saturation,onChange:P=>ee({on_saturation:P.target.value}),children:[l.jsx("option",{value:"pause",children:"pause (preserves latency accuracy)"}),l.jsx("option",{value:"drop_oldest",children:"drop_oldest"})]})]})]}),w&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.test.record_execs,onChange:P=>ee({record_execs:P.target.checked})}),"Record per-exec detail — one CSV row per ack/fill with latency + inter-report gap (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."]})]}),w&&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:S.enabled,onChange:P=>me({enabled:P.target.checked})}),"Amend working orders — price-only OrderCancelReplaceRequest fired every nth fill"]}),S.enabled&&l.jsxs(l.Fragment,{children:[l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Every n fills"}),l.jsx("input",{type:"number",min:1,value:S.every_n_fills,onChange:P=>me({every_n_fills:Number(P.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:S.amend_ratio,onChange:P=>me({amend_ratio:Number(P.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:S.max_per_order,onChange:P=>me({max_per_order:Number(P.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:S.price_offset,onChange:P=>me({price_offset:Number(P.target.value)})})]})]}),_&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:S.pending_replace,onChange:P=>me({pending_replace:P.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)."}),_&&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."]})]})]}),w&&l.jsxs("div",{className:`perf-form-section ${k?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Order payload",k&&l.jsx("span",{className:"perf-layer-badge",children:"values for template"})]}),k&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Order template ",l.jsx("strong",{children:W})," 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:P=>i(P.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:P=>de({side:P.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 ${k?"perf-field-overridden":""}`,children:[l.jsxs("span",{children:["Order type ",k&&l.jsx("em",{className:"perf-field-tag",children:"from template"})]}),l.jsxs("select",{value:r.payload.order.ord_type,disabled:k,onChange:P=>de({ord_type:P.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:P=>de({quantity_min:Number(P.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:P=>de({quantity_max:Number(P.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:P=>de({price_min:Number(P.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:P=>de({price_max:Number(P.target.value)})})]})]})]}),w&&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:P=>R({auto_expiry:P.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:P=>R({expiry_offset:Number(P.target.value)})}),l.jsxs("select",{value:r.payload.expiry_unit??"days",onChange:P=>R({expiry_unit:P.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"})]})]}),ie.map((P,B)=>l.jsxs("div",{className:"perf-field-row",children:[l.jsx("input",{style:{width:72},placeholder:"tag",value:P.tag,onChange:it=>F(B,{tag:it.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:P.value,onChange:it=>F(B,{value:it.target.value})}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:()=>z(ie.filter((it,Er)=>Er!==B)),children:"✕"})]},B)),l.jsx("div",{className:"perf-field-row",children:l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>z([...ie,{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."]})]}),_&&l.jsxs("div",{className:`perf-form-section ${$?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Fill payload",$&&l.jsx("span",{className:"perf-layer-badge",children:"behavior for template"})]}),$&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Exec template ",l.jsx("strong",{children:G})," 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:P=>Oe({fill_type:P.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"}),l.jsx("input",{type:"number",min:1,value:r.payload.fill.fills_per_order,onChange:P=>Oe({fills_per_order:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.payload.fill.send_ack,onChange:P=>Oe({send_ack:P.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:P=>Oe({partial_fill_pct_min:Number(P.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:P=>Oe({partial_fill_pct_max:Number(P.target.value)})})]})]})]})]}),(w||_)&&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:[w&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order template"}),l.jsxs("select",{value:r.payload.order_template_id??"",onChange:P=>R({order_template_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),L.map(P=>l.jsx("option",{value:P.id,children:P.name},P.id))]})]}),_&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Exec report template"}),l.jsxs("select",{value:r.payload.exec_template_id??"",onChange:P=>R({exec_template_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),H.map(P=>l.jsx("option",{value:P.id,children:P.name},P.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:()=>s(P=>!P),children:[o?"▾":"▸"," Advanced"]}),o&&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:P=>J({correlation_tag:Number(P.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:P=>J({exec_id_tag:Number(P.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:P=>ee({max_pending:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.rate.allow_burst,onChange:P=>ae({allow_burst:P.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:P=>ae({max_burst_multiplier:Number(P.target.value)})})]})]})]}),l.jsxs("div",{className:"perf-form-actions",children:[T?l.jsx("span",{className:"perf-not-ready",children:T}):I&&!(N&&A)?l.jsxs("span",{className:"perf-not-ready",children:["Selected session",w&&_?"s":""," must be LOGGED_ON before starting."]}):null,l.jsx("button",{className:"btn btn-primary",onClick:M,disabled:!C,title:I?T||(N&&A?void 0:"Session not logged on yet"):"Select the required session(s)",children:f?"Starting…":"▶ Start run"})]})]})}function Yp({status:e}){const t=e==="LOGGED_ON";return l.jsxs("span",{className:`perf-status-chip ${t?"ok":"warn"}`,children:[t?"● ":"○ ",e]})}const Xp=600,oW=5e3,sW=new Set(["completed","stopped","error"]);function lW(e){const[t,r]=h.useState(null),[n,a]=h.useState([]),[i,o]=h.useState(!1);return h.useEffect(()=>{if(r(null),a([]),o(!1),!e)return;let s=!1,c=null,u,f=0,d=!1;async function m(){if(s||d||!e)return;let v;try{({ticket:v}=await Z.perf.runs.ticket(e))}catch{p();return}if(s)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=()=>{s||(f=0,o(!0))},c.onmessage=b=>{let x;try{x=JSON.parse(b.data)}catch{return}r(x),a(w=>{var j,O,N,A,I,T,C,U,L;const _={t:x.elapsed_s,ops:((j=x.client)==null?void 0:j.ops_live)??0,fps:((O=x.venue)==null?void 0:O.fps_live)??0,aps:((N=x.client)==null?void 0:N.aps_live)??0,tps:(((A=x.client)==null?void 0:A.ops_live)??0)+(((I=x.venue)==null?void 0:I.fps_live)??0),p50:(((T=x.response_latency)==null?void 0:T.p50_us)??0)/1e3,p99:(((C=x.response_latency)==null?void 0:C.p99_us)??0)/1e3,fc50:(((U=x.fill_completion_latency)==null?void 0:U.p50_us)??0)/1e3,fc99:(((L=x.fill_completion_latency)==null?void 0:L.p99_us)??0)/1e3},S=[...w,_];return S.length>Xp?S.slice(-Xp):S}),sW.has(x.status)&&(d=!0,c==null||c.close())},c.onclose=()=>{s||(o(!1),d||p())},c.onerror=()=>{c==null||c.close()}}function p(){if(s||d)return;f+=1;const v=Math.min(oW,500*2**Math.min(f,4));u=setTimeout(m,v)}return m(),()=>{s=!0,clearTimeout(u),c==null||c.close()}},[e]),{latest:t,series:n,connected:i}}const Lw="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",s=await(r==="png"?n:a)(e,{backgroundColor:i,pixelRatio:2,quality:.95,filter:u=>!(u instanceof HTMLElement&&u.classList.contains(Lw))}),c=document.createElement("a");c.href=s,c.download=t,c.click()}function uW(e,t){const r=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");return`${e}-${r}.${t}`}const Pl=new Set(["completed","stopped","error"]);function vr(e){return e?(e/1e3).toFixed(3):"—"}function Fw(e,t){return t<=0?"—":(e/t).toFixed(2)}function Nl({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:vr(r[n])}),l.jsxs("span",{className:"perf-lat-lbl",children:[n.replace("_us","")," ms"]})]},n))})]})}function dW({runId:e}){var w,_;const{latest:t,series:r,connected:n}=lW(e),[a,i]=h.useState(null),[o,s]=h.useState(!1),c=h.useRef(null);h.useEffect(()=>{let S=!0;i(null);let j;const O=async()=>{try{const N=await Z.perf.runs.get(e);if(!S)return;i(N),Pl.has(N.status)&&clearInterval(j)}catch{}};return j=setInterval(O,2e3),O(),()=>{S=!1,clearInterval(j)}},[e]);const u=t?Pl.has(t.status):a?Pl.has(a.status):!1,f=!u;async function d(){try{await Z.perf.runs.stop(e)}catch{}}async function m(S){if(!(!c.current||o)){s(!0);try{const j=S==="png"?"png":"jpg";await cW(c.current,uW(`perf-${e.slice(0,8)}`,j),S)}catch(j){console.error("image export failed",j)}finally{s(!1)}}}const p=t==null?void 0:t.client,v=t==null?void 0:t.venue,y=t==null?void 0:t.errors,g=(y==null?void 0:y.saturated)||(t==null?void 0:t.status)==="saturated",b=t==null?void 0:t.rate_config,x=[];return p&&(x.push({label:"orders/s",value:Math.round(p.ops_live).toLocaleString()}),(b==null?void 0:b.orders_per_window)!=null&&(b==null?void 0:b.order_window_ms)!=null&&x.push({label:"order rate (cfg)",value:`${b.orders_per_window.toLocaleString()} / ${b.order_window_ms}ms`}),x.push({label:"orders sent",value:p.orders_sent.toLocaleString()},{label:"pending",value:p.pending.toLocaleString()},{label:"dropped",value:p.dropped.toLocaleString()}),(p.amends_sent>0||p.aps_live>0)&&(x.push({label:"amends/s",value:Math.round(p.aps_live).toLocaleString()},{label:"amends",value:p.amends_sent.toLocaleString()},{label:"replaces",value:p.replaces_received.toLocaleString()}),p.amends_lost>0&&x.push({label:"amends lost",value:p.amends_lost.toLocaleString()}))),v&&(x.push({label:"fills/s",value:Math.round(v.fps_live).toLocaleString()}),(b==null?void 0:b.fills_per_window)!=null&&(b==null?void 0:b.fill_window_ms)!=null&&x.push({label:"fill rate (cfg)",value:`${b.fills_per_window.toLocaleString()} / ${b.fill_window_ms}ms`}),x.push({label:"fills",value:v.fills_sent.toLocaleString()},{label:"fills/order",value:Fw(v.fills_sent,v.orders_received-v.unfilled)},{label:"fill ratio",value:v.fill_ratio.toFixed(3)},{label:"recv",value:v.orders_received.toLocaleString()},{label:"acks",value:v.acks_sent.toLocaleString()},{label:"unfilled",value:v.unfilled.toLocaleString()}),v.amends_received>0&&x.push({label:"amends recv",value:v.amends_received.toLocaleString()})),y&&x.push({label:"lost",value:y.lost_timeout.toLocaleString()},{label:"rejected",value:y.rejected.toLocaleString()}),l.jsxs("div",{className:"perf-dashboard",ref:c,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 ${Lw}`,children:[f&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:d,children:"■ Stop"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Z.perf.runs.export(e,"messages"),children:"Export CSV"}),u&&l.jsx("button",{className:"btn btn-sm btn-ghost",title:"Per-exec detail (requires record_execs)",onClick:()=>Z.perf.runs.export(e,"execs"),children:"Execs 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:o,onClick:()=>m("png"),children:o?"…":"PNG"}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:o,onClick:()=>m("jpeg"),children:o?"…":"JPG"})]})]}),g&&l.jsxs("div",{className:"perf-saturated-banner",children:["⚠ SATURATED — injection paused to protect latency measurement.",p&&l.jsxs(l.Fragment,{children:[" Pending ",p.pending.toLocaleString(),", dropped ",p.dropped.toLocaleString(),"."]})]}),(w=a==null?void 0:a.warnings)==null?void 0:w.map((S,j)=>l.jsxs("div",{className:"perf-warning-banner",children:["⚠ ",S]},j)),!t&&!u&&l.jsx("div",{className:"loading-msg",children:"Waiting for first snapshot…"}),x.length>0&&l.jsx("div",{className:"stat-cards",children:x.map(({label:S,value:j})=>l.jsxs("div",{className:"stat-card",children:[l.jsx("span",{className:"stat-card-value",children:j}),l.jsx("span",{className:"stat-card-label",children:S})]},S))}),t&&l.jsxs("div",{className:"perf-lat-row",children:[l.jsx(Nl,{title:"Response latency",sub:"order-2-ack",stats:t.response_latency}),l.jsx(Nl,{title:"Fill-completion latency",sub:"order-2-last-fill",stats:t.fill_completion_latency}),((_=t.amend_latency)==null?void 0:_.count)>0&&l.jsx(Nl,{title:"Amend latency",sub:"amend-2-replaced",stats:t.amend_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(Nn,{width:"100%",height:200,children:l.jsxs(Ti,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Zr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Kr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:S=>`${Math.round(S)}s`}),l.jsx(qr,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(An,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:S=>`${Math.round(Number(S))}s`}),l.jsx(ga,{wrapperStyle:{fontSize:11}}),l.jsx(yt,{type:"monotone",dataKey:"ops",name:"orders/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(yt,{type:"monotone",dataKey:"fps",name:"fills/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1}),((p==null?void 0:p.amends_sent)??0)>0&&l.jsx(yt,{type:"monotone",dataKey:"aps",name:"amends/s",stroke:"#c084fc",dot:!1,isAnimationActive:!1}),l.jsx(yt,{type:"monotone",dataKey:"tps",name:"total msgs/s",stroke:"#fbbf24",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(Nn,{width:"100%",height:200,children:l.jsxs(Ti,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Zr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Kr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:S=>`${Math.round(S)}s`}),l.jsx(qr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(An,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:S=>`${Math.round(Number(S))}s`}),l.jsx(ga,{wrapperStyle:{fontSize:11}}),l.jsx(yt,{type:"monotone",dataKey:"p50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(yt,{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(Nn,{width:"100%",height:200,children:l.jsxs(Ti,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Zr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Kr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:S=>`${Math.round(S)}s`}),l.jsx(qr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(An,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:S=>`${Math.round(Number(S))}s`}),l.jsx(ga,{wrapperStyle:{fontSize:11}}),l.jsx(yt,{type:"monotone",dataKey:"fc50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(yt,{type:"monotone",dataKey:"fc99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})})]}),u&&(a==null?void 0:a.summary)&&l.jsx(fW,{status:a})]})}function fW({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)",Fw(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)",`${vr(t.response_latency.p50_us)} / ${vr(t.response_latency.p99_us)}`],["Response max (ms)",vr(t.response_latency.max_us)],["Fill-completion p50 / p99 (ms)",`${vr(t.fill_completion_latency.p50_us)} / ${vr(t.fill_completion_latency.p99_us)}`]];return t.amends_sent&&(r.push(["Amends sent / replaced / lost",`${t.amends_sent} / ${t.replaces_received??0} / ${t.amends_lost??0}`]),t.amend_latency&&r.push(["Amend p50 / p99 (ms)",`${vr(t.amend_latency.p50_us)} / ${vr(t.amend_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 hW=new Set(["completed","stopped","error"]);function mW(e){if(!e)return"—";try{return new Date(e*1e3).toLocaleTimeString()}catch{return"—"}}function pW({activeRunId:e,onSelect:t,refreshKey:r}){const[n,a]=h.useState([]);h.useEffect(()=>{let o=!0;const s=()=>Z.perf.runs.list().then(u=>{o&&a(u)}).catch(()=>{});s();const c=setInterval(s,2e3);return()=>{o=!1,clearInterval(c)}},[r]);async function i(o,s){o.stopPropagation();try{await Z.perf.runs.stop(s)}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(o=>l.jsxs("tr",{className:o.run_id===e?"perf-run-active":"",onClick:()=>t(o.run_id),children:[l.jsx("td",{children:o.name}),l.jsx("td",{children:o.mode}),l.jsx("td",{children:l.jsx("span",{className:`badge perf-state-${o.status}`,children:o.status})}),l.jsx("td",{children:mW(o.started_at)}),l.jsxs("td",{className:"mono",children:[Math.round(o.elapsed_s),"s"]}),l.jsx("td",{className:"mono",children:o.summary?o.summary.orders_sent.toLocaleString():"—"}),l.jsx("td",{className:"mono",children:o.summary?o.summary.fill_ratio.toFixed(2):"—"}),l.jsx("td",{className:"perf-runlist-actions",children:!hW.has(o.status)&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:s=>i(s,o.run_id),children:"Stop"})})]},o.run_id))})]})}const Cl=200,On=(e,t,r=120)=>({field:e,headerName:t,width:r,type:"numericColumn",valueFormatter:n=>n.value==null?"":Number(n.value).toLocaleString()}),Ac=(e,t)=>({field:e,headerName:t,width:150,type:"numericColumn",valueFormatter:r=>r.value==null?"":`${Math.round(Number(r.value)).toLocaleString()}`}),vW=[{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},On("qty","Qty",90),On("price","Price",100),Ac("response_latency_us","Resp µs"),Ac("fill_latency_us","Fill µs"),On("fill_qty","Fill qty",100),On("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}],gW=[{field:"scenario_name",headerName:"Scenario",flex:1,minWidth:160},Ac("latency_us","Latency µs"),On("msg_count","Msgs",90),On("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 yW({runId:e}){const t=V(g=>g.theme),[r,n]=h.useState("messages"),[a,i]=h.useState(0),[o,s]=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:Cl,offset:a*Cl}).then(x=>{g&&(s(x.items),u(x.total))}).catch(()=>{g&&(s([]),u(0))}).finally(()=>{g&&d(!1)}),()=>{g=!1}},[e,r,a]);const m=h.useMemo(()=>r==="messages"?vW:gW,[r]),p=h.useMemo(()=>({sortable:!0,filter:!0,resizable:!0}),[]),v=Math.max(0,Math.ceil(c/Cl)-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(kc,{rowData:o,columnDefs:m,defaultColDef:p,animateRows:!1,suppressCellFocus:!0})})]})}function bW(){const{theme:e,toggleTheme:t,branding:r,setSessions:n}=V(),a=Gs(),i=r!=null&&r.prefix?`${r.prefix} FIXture`:"FIXture";Pv(),h.useEffect(()=>{Z.sessions.list().then(n).catch(()=>{})},[]);const[o,s]=h.useState("configure"),[c,u]=h.useState(null),[f,d]=h.useState(0);function m(y){u(y),d(g=>g+1),s("live")}function p(y){u(y),s("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(Yr,{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${o===y.id?" active":""}`,disabled:y.disabled,onClick:()=>s(y.id),children:y.label},y.id))}),l.jsxs("div",{className:"perf-body",children:[o==="configure"&&l.jsx("div",{className:"perf-pane perf-pane-form",children:l.jsx(iW,{onStarted:m})}),o==="live"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(dW,{runId:c},c)}),o==="results"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(yW,{runId:c},c)}),o==="history"&&l.jsx("div",{className:"perf-pane",children:l.jsx(pW,{activeRunId:c,onSelect:p,refreshKey:f})})]})]})}function xW(){return V(t=>t.token)?l.jsx(hv,{}):l.jsx(Ri,{to:"/login",replace:!0})}function wW(){const e=V(t=>t.user);return e?e.role!=="platform_admin"?l.jsx(Ri,{to:"/",replace:!0}):l.jsx(hv,{}):l.jsx(Ri,{to:"/login",replace:!0})}function jW(){const e=V(t=>t.setBranding);h.useEffect(()=>{fetch("/api/branding").then(t=>t.json()).then(t=>{e(t),Rw(t)}).catch(()=>{})},[])}function SW(){const e=on(),t=V(r=>r.theme);return jW(),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(Kj,{children:[l.jsx(Lt,{path:"/setup",element:l.jsx(Vz,{})}),l.jsx(Lt,{path:"/login",element:l.jsx(Xz,{})}),l.jsx(Lt,{path:"/register",element:l.jsx(Jz,{})}),l.jsxs(Lt,{element:l.jsx(xW,{}),children:[l.jsx(Lt,{path:"/",element:l.jsx(Yz,{})}),l.jsxs(Lt,{element:l.jsx(wW,{}),children:[l.jsx(Lt,{path:"/admin/users",element:l.jsx(nW,{})}),l.jsx(Lt,{path:"/perf",element:l.jsx(bW,{})})]})]}),l.jsx(Lt,{path:"*",element:l.jsx(Ri,{to:"/",replace:!0})})]})}El.createRoot(document.getElementById("root")).render(l.jsx(Vp.StrictMode,{children:l.jsx(gS,{children:l.jsx(SW,{})})}));
|
|
102
|
+
}`;function qz({onClose:e}){const[t,r]=h.useState(""),[n,a]=h.useState(""),[i,o]=h.useState(!1);h.useEffect(()=>{Z.specOverlay.get().then(c=>r(JSON.stringify(c,null,2))).catch(console.error)},[]);async function s(){a("");let c;try{c=JSON.parse(t)}catch{a("Invalid JSON");return}o(!0);try{await Z.specOverlay.set(c),e()}catch(u){a(u instanceof Error?u.message:"Save failed")}finally{o(!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:Kz})}),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:s,disabled:i,children:i?"Saving…":"Save overlay"})]})]})})}const Mw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgCgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGNpcmNsZSBjeD0iMjA3IiBjeT0iNzgiIHI9IjEwIiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMWEzYTVjIiBzdHlsZT0iZmlsbDpyZ2IoMjYsIDU4LCA5Mik7c3Ryb2tlOm5vbmU7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjY1IiB4Mj0iMjQ4IiB5Mj0iNjUiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDAsIDE4MCwgMjAwKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzAwYjRjOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMCwgMTgwLCAyMDApO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNztmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI5MSIgeDI9IjI0NCIgeTI9IjkxIiBzdHJva2U9IiMwMGI0YzgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjQ1IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigwLCAxODAsIDIwMCk7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC40NTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgoKICA8dGV4dCB4PSIyNzIiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtbW9ubykiIGZvbnQtc2l6ZT0iNjYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6NTBweDtmb250LXdlaWdodDozMDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byI+dHVyZTwvdGV4dD4KPC9zdmc+",Dw="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgdmlld0JveD0iMCAwIDY4MCAxNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogIDxwb2x5Z29uIHBvaW50cz0iMTg4LDI4IDIzOCwyOCAyNTgsNzggMjM4LDEyOCAxODgsMTI4IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSIxMCIgZmlsbD0iIzFhM2E1YyIgc3R5bGU9ImZpbGw6cmdiKDI2LCA1OCwgOTIpO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxjaXJjbGUgY3g9IjIwNyIgY3k9Ijc4IiByPSI2IiBmaWxsPSIjMDBiNGM4IiBzdHlsZT0iZmlsbDpyZ2IoMCwgMTgwLCAyMDApO3N0cm9rZTpub25lO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MTtmb250LWZhbWlseTomcXVvdDtBbnRocm9waWMgU2FucyZxdW90OywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtTZWdvZSBVSSZxdW90Oywgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmb250LXdlaWdodDo0MDA7dGV4dC1hbmNob3I6c3RhcnQ7ZG9taW5hbnQtYmFzZWxpbmU6YXV0byIvPgogIDxsaW5lIHgxPSIyMjAiIHkxPSI2NSIgeDI9IjI0OCIgeTI9IjY1IiBzdHJva2U9IiMxYTNhNWMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHlsZT0iZmlsbDpyZ2IoMCwgMCwgMCk7c3Ryb2tlOnJnYigyNiwgNTgsIDkyKTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjRweDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46bWl0ZXI7b3BhY2l0eToxO2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9Ijc4IiB4Mj0iMjUyIiB5Mj0iNzgiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDAsIDAsIDApO3N0cm9rZTpyZ2IoMjYsIDU4LCA5Mik7Y29sb3I6cmdiKDI1NSwgMjU1LCAyNTUpO3N0cm9rZS13aWR0aDo0cHg7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO29wYWNpdHk6MC43O2ZvbnQtZmFtaWx5OiZxdW90O0FudGhyb3BpYyBTYW5zJnF1b3Q7LCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICZxdW90O1NlZ29lIFVJJnF1b3Q7LCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFuY2hvcjpzdGFydDtkb21pbmFudC1iYXNlbGluZTphdXRvIi8+CiAgPGxpbmUgeDE9IjIyMCIgeTE9IjkxIiB4Mj0iMjQ0IiB5Mj0iOTEiIHN0cm9rZT0iIzFhM2E1YyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9IjAuNDUiIHN0eWxlPSJmaWxsOnJnYigwLCAwLCAwKTtzdHJva2U6cmdiKDI2LCA1OCwgOTIpO2NvbG9yOnJnYigyNTUsIDI1NSwgMjU1KTtzdHJva2Utd2lkdGg6NHB4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjAuNDU7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJnF1b3Q7U2Vnb2UgVUkmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7Zm9udC13ZWlnaHQ6NDAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iLz4KCiAgPHRleHQgeD0iMjcyIiB5PSIxMDUiIGZvbnQtZmFtaWx5PSJ2YXIoLS1mb250LW1vbm8pIiBmb250LXNpemU9IjY2IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMWEzYTVjIiBsZXR0ZXItc3BhY2luZz0iLTEiIHN0eWxlPSJmaWxsOnJnYigyNiwgNTgsIDkyKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6dWktbW9ub3NwYWNlLCBtb25vc3BhY2U7Zm9udC1zaXplOjY2cHg7Zm9udC13ZWlnaHQ6NzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPkZJWDwvdGV4dD4KICA8dGV4dCB4PSIzODYiIHk9IjEwNSIgZm9udC1mYW1pbHk9InZhcigtLWZvbnQtc2FucykiIGZvbnQtc2l6ZT0iNTAiIGZvbnQtd2VpZ2h0PSIzMDAiIGZpbGw9IiMwMGI0YzgiIGxldHRlci1zcGFjaW5nPSItMSIgc3R5bGU9ImZpbGw6cmdiKDAsIDE4MCwgMjAwKTtzdHJva2U6bm9uZTtjb2xvcjpyZ2IoMjU1LCAyNTUsIDI1NSk7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtvcGFjaXR5OjE7Zm9udC1mYW1pbHk6JnF1b3Q7QW50aHJvcGljIFNhbnMmcXVvdDssIHNhbnMtc2VyaWY7Zm9udC1zaXplOjUwcHg7Zm9udC13ZWlnaHQ6MzAwO3RleHQtYW5jaG9yOnN0YXJ0O2RvbWluYW50LWJhc2VsaW5lOmF1dG8iPnR1cmU8L3RleHQ+Cjwvc3ZnPg==";function Gs(){const e=V(r=>r.theme),t=V(r=>r.branding);return e==="dark"?(t==null?void 0:t.logo_dark)||Mw:(t==null?void 0:t.logo_light)||Dw}function Yz(){const e=V(g=>g.setSessions),t=V(g=>g.theme),r=V(g=>g.toggleTheme),n=V(g=>g.user),a=V(g=>g.logout),i=V(g=>g.openTabs),o=V(g=>g.activeTabId),s=V(g=>g.branding),c=Gs(),u=s!=null&&s.prefix?`${s.prefix} FIXture`:"FIXture",[f,d]=h.useState(!1),[m,p]=h.useState(!1),[v,y]=h.useState(!1);return Pv(),h.useEffect(()=>{document.documentElement.setAttribute("data-theme",t)},[t]),h.useEffect(()=>{Z.sessions.list().then(e).catch(console.error)},[]),h.useEffect(()=>{if(!n){a_();return}Z.customTags.list().then(Ov).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(Yr,{to:"/perf",className:"btn btn-sm btn-ghost",title:"Performance testing",children:"Perf"}),(n==null?void 0:n.role)==="platform_admin"&&l.jsx(Yr,{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(y_,{}),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(kz,{}),l.jsx("div",{className:"tab-panels",children:i.map(g=>l.jsx("div",{className:`tab-panel${g===o?" active":""}`,children:l.jsx(Ez,{sessionId:g})},g))})]})})]}),f&&l.jsx(zz,{onClose:()=>d(!1)}),m&&l.jsx(Zz,{onClose:()=>p(!1)}),v&&l.jsx(qz,{onClose:()=>y(!1)})]})}function Xz(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),[c,u]=h.useState(!1),{login:f}=V(),d=Gs(),m=on();h.useEffect(()=>{fetch("/api/auth/register/status").then(v=>v.json()).then(v=>u(v.enabled)).catch(()=>{})},[]);async function p(v){v.preventDefault(),i(""),s(!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{s(!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:o,children:o?"Signing in…":"Sign in"})]}),c&&l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["No account? ",l.jsx(Yr,{to:"/register",children:"Register"})]})]})})}function Vz(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),c=Gs(),u=on();async function f(d){d.preventDefault(),i(""),s(!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{s(!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:o,children:o?"Creating…":"Create admin"})]})]})})}function Jz(){const[e,t]=h.useState(""),[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),[c,u]=h.useState(null),{login:f,theme:d}=V(),m=on();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(""),s(!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{s(!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"?Mw:Dw,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:o,children:o?"Creating account…":"Create account"})]}),l.jsxs("p",{className:"auth-hint",style:{marginTop:12},children:["Already have an account? ",l.jsx(Yr,{to:"/login",children:"Sign in"})]})]})})}const Qz=["accent","bg","bg2","bg3","border","text","text2"];function Rw(e){let t=document.getElementById("branding-style");t||(t=document.createElement("style"),t.id="branding-style",document.head.appendChild(t));const r=Qz.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 Ol(e){return new Promise((t,r)=>{const n=new FileReader;n.onload=()=>t(n.result),n.onerror=r,n.readAsDataURL(e)})}const $w=["user","user_admin","platform_admin"];function eW(e){return new Date(e*1e3).toLocaleString()}function tW({onClose:e,onCreated:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=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:o});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:o,onChange:p=>s(p.target.value),children:$w.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 rW({user:e,onClose:t}){const[r,n]=h.useState(""),[a,i]=h.useState(""),[o,s]=h.useState(!1),[c,u]=h.useState(!1);async function f(d){d.preventDefault(),i(""),s(!0);try{await Z.admin.users.update(e.uid,{password:r}),u(!0)}catch(m){i(m.message)}finally{s(!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:o,children:o?"Saving…":"Save"})]})]})]})})}function nW(){const e=V(k=>k.user),t=on(),[r,n]=h.useState([]),[a,i]=h.useState(null),[o,s]=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}=V(),[b,x]=h.useState(!1),[w,_]=h.useState(null),[S,j]=h.useState("");async function O(){try{const[k,$,W,G]=await Promise.all([Z.admin.users.list(),Z.admin.settings.get(),Z.admin.housekeeping.get(),Z.admin.branding.get()]);n(k),i($),s(W),p(G)}catch(k){j(k.message)}}h.useEffect(()=>{O()},[]);async function N(k){try{const $=await Z.admin.users.update(k.uid,{is_active:!k.is_active});n(W=>W.map(G=>G.uid===k.uid?$:G))}catch($){j($.message)}}async function A(k,$){try{const W=await Z.admin.users.update(k.uid,{role:$});n(G=>G.map(J=>J.uid===k.uid?W:J))}catch(W){j(W.message)}}async function I(k){if(confirm(`Delete user "${k.username}"? This cannot be undone.`))try{await Z.admin.users.delete(k.uid),n($=>$.filter(W=>W.uid!==k.uid))}catch($){j($.message)}}async function T(){if(a)try{const k=await Z.admin.settings.update({registration_enabled:!a.enabled});i(k)}catch(k){j(k.message)}}async function C(k){const $=parseInt(k,10);if(!(isNaN($)||$<1))try{const W=await Z.admin.settings.update({max_users:$});i(W)}catch(W){j(W.message)}}async function U(){if(o)try{const k=await Z.admin.housekeeping.update({enabled:!o.enabled});s(k)}catch(k){j(k.message)}}async function L(k,$){const W=parseInt($,10);if(!(isNaN(W)||W<0))try{const G=await Z.admin.housekeeping.update({[k]:W});s(G)}catch(G){j(G.message)}}async function H(){d(!0),u(null);try{const k=await Z.admin.housekeeping.runNow();u(`Done — deleted ${k.msgs_deleted} messages, ${k.logs_deleted} log files`)}catch(k){j(k.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:k=>C(k.target.value)}),l.jsxs("span",{className:"admin-settings-label",style:{color:"var(--text2)"},children:["(",a.current_users," / ",a.max_users," registered)"]})]}),o&&l.jsxs("div",{className:"admin-settings-bar",children:[l.jsx("span",{className:"admin-settings-label",children:"Housekeeping:"}),l.jsx("button",{className:`btn btn-sm ${o.enabled?"btn-stop":"btn-start"}`,onClick:U,children:o.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:o.msg_retention_days,min:0,title:"0 = keep forever",onBlur:k=>L("msg_retention_days",k.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:o.log_retention_days,min:0,title:"0 = keep forever",onBlur:k=>L("log_retention_days",k.target.value)}),l.jsx("button",{className:"btn btn-sm btn-ghost",style:{marginLeft:16},onClick:H,disabled:f,children:f?"Running…":"Run Now"}),c&&l.jsx("span",{style:{marginLeft:8,color:"var(--green)",fontSize:12},children:c})]}),m&&l.jsxs("div",{className:"admin-settings-bar",style:{flexDirection:"column",alignItems:"flex-start",gap:10},children:[l.jsx("span",{className:"admin-settings-label",style:{fontWeight:700},children:"Branding"}),l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[l.jsx("span",{className:"admin-settings-label",children:"Brand prefix:"}),l.jsx("input",{className:"admin-max-users-input",style:{width:160},value:m.prefix,placeholder:"e.g. Acme (shown as Acme FIXture)",onChange:k=>p({...m,prefix:k.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:k=>p({...m,accent:k.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(([k,$,W])=>l.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[l.jsxs("span",{className:"admin-settings-label",children:[$,":"]}),l.jsx("input",{type:"color",value:m[k]||W,onChange:G=>p({...m,[k]:G.target.value}),title:$}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>p({...m,[k]:""}),title:"Reset to default",children:"↺"})]},k))}),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 k=>{var W;const $=(W=k.target.files)==null?void 0:W[0];$&&p({...m,logo_dark:await Ol($)})}}),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 k=>{var W;const $=(W=k.target.files)==null?void 0:W[0];$&&p({...m,logo_light:await Ol($)})}}),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 k=>{var W;const $=(W=k.target.files)==null?void 0:W[0];$&&p({...m,favicon:await Ol($)})}}),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 k=await Z.admin.branding.update(m);p(k),g(k),Rw(k)}catch(k){j(k.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(k=>l.jsxs("tr",{className:k.is_active?"":"admin-row-inactive",children:[l.jsxs("td",{children:[k.username,k.uid===(e==null?void 0:e.uid)&&l.jsx("span",{className:"admin-you-badge",children:" (you)"})]}),l.jsx("td",{children:l.jsx("select",{value:k.role,onChange:$=>A(k,$.target.value),className:"admin-role-select",disabled:k.uid===(e==null?void 0:e.uid),children:$w.map($=>l.jsx("option",{value:$,children:$},$))})}),l.jsx("td",{children:l.jsx("span",{style:{color:k.is_active?"var(--green)":"var(--text2)"},children:k.is_active?"Active":"Inactive"})}),l.jsx("td",{style:{color:"var(--text2)",fontSize:11},children:eW(k.created_at)}),l.jsx("td",{children:l.jsxs("div",{className:"admin-row-actions",children:[l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>N(k),disabled:k.uid===(e==null?void 0:e.uid),title:k.is_active?"Deactivate":"Activate",children:k.is_active?"Deactivate":"Activate"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>_(k),title:"Reset password",children:"Reset PW"}),l.jsx("button",{className:"btn btn-sm btn-danger",onClick:()=>I(k),disabled:k.uid===(e==null?void 0:e.uid),title:"Delete user",children:"✕"})]})})]},k.uid))})]}),b&&l.jsx(tW,{onClose:()=>x(!1),onCreated:k=>n($=>[...$,k])}),w&&l.jsx(rW,{user:w,onClose:()=>_(null)})]})}const Ec={enabled:!1,every_n_fills:1,amend_ratio:1,max_per_order:1,price_offset:.01,pending_replace:!1},Kp={name:"smoke",mode:"loopback",client_session_id:null,venue_session_id:null,correlation_tag:376,exec_id_tag:25116,rate:{orders_per_window:50,order_window_ms:100,dispatch:"smooth",fill_dispatch:"smooth",fills_per_window:50,fill_window_ms:100,fill_ratio:1,allow_burst:!1,max_burst_multiplier:2},test:{duration:30,max_orders:0,scenario_timeout_ms:3e4,max_pending:1e5,on_saturation:"pause",output:"results",record_execs:!1},amend:Ec,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:{}}},aW=["loopback","manager","client","venue"];function qp(e,t){return t?Math.round(e/t*1e3):0}function iW({onStarted:e}){var ea,D;const t=V(P=>P.sessions),[r,n]=h.useState(Kp),[a,i]=h.useState(Kp.payload.order.symbols.join(", ")),[o,s]=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==="loopback"||r.mode==="manager"||r.mode==="client",_=r.mode==="loopback"||r.mode==="manager"||r.mode==="venue",S=r.amend??Ec,j=r.client_session_id?t[r.client_session_id]:void 0,O=r.venue_session_id?t[r.venue_session_id]:void 0,N=!w||(j==null?void 0:j.status)==="LOGGED_ON",A=!_||(O==null?void 0:O.status)==="LOGGED_ON",I=(!w||!!r.client_session_id)&&(!_||!!r.venue_session_id),T=(()=>{if(!r.name.trim())return"Name is required";const P=r.rate;if(P.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(w){if(P.orders_per_window<1||P.order_window_ms<1)return"Order rate must be > 0";if(a.split(",").map(it=>it.trim()).filter(Boolean).length===0)return"At least one symbol is required";const B=r.payload.order;if(B.quantity_min<1||B.quantity_max<1||B.quantity_min>B.quantity_max)return"Check quantity min/max";if(B.price_min<=0||B.price_max<=0||B.price_min>B.price_max)return"Check price min/max";if(S.enabled){if(S.every_n_fills<1)return"Amend every-n-fills must be ≥ 1";if(S.amend_ratio<0||S.amend_ratio>1)return"Amend ratio must be between 0 and 1";if(S.max_per_order<0)return"Max amends per order must be ≥ 0";if(S.price_offset<=0)return"Amend price offset must be > 0"}}if(_){if(P.fills_per_window<1||P.fill_window_ms<1)return"Fill rate must be > 0";if(r.payload.fill.fills_per_order<1)return"Fills per order must be ≥ 1";if(P.fill_ratio<0||P.fill_ratio>1)return"Fill ratio must be between 0 and 1"}return null})(),C=I&&N&&A&&!T&&!f;async function U(){try{p(await Z.perf.configs.list())}catch{}}h.useEffect(()=>{U()},[]),h.useEffect(()=>{Z.templates.list().then(b).catch(()=>{})},[]);const L=g.filter(P=>P.msg_type==="D"),H=g.filter(P=>P.msg_type==="8"),k=!!r.payload.order_template_id,$=!!r.payload.exec_template_id,W=((ea=L.find(P=>P.id===r.payload.order_template_id))==null?void 0:ea.name)??"selected",G=((D=H.find(P=>P.id===r.payload.exec_template_id))==null?void 0:D.name)??"selected",J=P=>n(B=>({...B,...P})),ae=P=>n(B=>({...B,rate:{...B.rate,...P}})),ee=P=>n(B=>({...B,test:{...B.test,...P}})),me=P=>n(B=>({...B,amend:{...Ec,...B.amend,...P}})),de=P=>n(B=>({...B,payload:{...B.payload,order:{...B.payload.order,...P}}})),Oe=P=>n(B=>({...B,payload:{...B.payload,fill:{...B.payload.fill,...P}}})),R=P=>n(B=>({...B,payload:{...B.payload,...P}})),[ie,ne]=h.useState([]),z=P=>{ne(P),R({gen_rules:Object.fromEntries(P.filter(B=>B.tag.trim()).map(B=>[B.tag.trim(),B.value]))})},F=(P,B)=>z(ie.map((it,Er)=>Er===P?{...it,...B}:it));function Q(){const P=a.split(",").map(B=>B.trim()).filter(Boolean);return{...r,client_session_id:w?r.client_session_id:null,venue_session_id:_?r.venue_session_id:null,payload:{...r.payload,order:{...r.payload.order,symbols:P}}}}async function M(){u(""),d(!0);try{const{run_id:P}=await Z.perf.runs.create(Q());e(P)}catch(P){u(P.message??"Failed to start run")}finally{d(!1)}}async function Ie(){u("");try{await Z.perf.configs.save(Q()),await U()}catch(P){u(P.message??"Failed to save config")}}async function se(P){if(y(P),!!P){u("");try{const B=await Z.perf.configs.get(P);n(B),i(B.payload.order.symbols.join(", ")),ne(Object.entries(B.payload.gen_rules??{}).map(([it,Er])=>({tag:it,value:Er})))}catch(B){u(B.message??"Failed to load config")}}}async function ze(){if(v&&window.confirm("Delete this saved config?"))try{await Z.perf.configs.delete(v),y(""),await U()}catch(P){u(P.message??"Failed to delete config")}}const Te=P=>{const B=t[P];return B?`${B.display_name||`${B.sender_comp_id} → ${B.target_comp_id}`} (${B.status})`:P},_t=x.filter(P=>P.session_role!=="venue"),mn=x.filter(P=>P.session_role!=="client");return l.jsxs("div",{className:"perf-form",children:[c&&l.jsx("div",{className:"error-msg",children:c}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Saved configs"}),l.jsxs("div",{className:"perf-field-row",children:[l.jsxs("select",{value:v,onChange:P=>se(P.target.value),children:[l.jsx("option",{value:"",children:"(load a saved config…)"}),m.map(P=>l.jsxs("option",{value:P.config_id,children:[P.name," — ",P.mode]},P.config_id))]}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:Ie,children:"Save current"}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:ze,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:P=>J({name:P.target.value})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Mode"}),l.jsx("select",{value:r.mode,onChange:P=>J({mode:P.target.value}),children:aW.map(P=>l.jsx("option",{value:P,children:P},P))})]}),w&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Client session"}),l.jsxs("select",{value:r.client_session_id??"",onChange:P=>J({client_session_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),_t.map(P=>l.jsx("option",{value:P.session_id,children:Te(P.session_id)},P.session_id))]}),j&&l.jsx(Yp,{status:j.status})]}),_&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Venue session"}),l.jsxs("select",{value:r.venue_session_id??"",onChange:P=>J({venue_session_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"(select…)"}),mn.map(P=>l.jsx("option",{value:P.session_id,children:Te(P.session_id)},P.session_id))]}),O&&l.jsx(Yp,{status:O.status})]})]}),l.jsxs("div",{className:"perf-form-section",children:[l.jsx("div",{className:"perf-section-title",children:"Rate"}),l.jsxs("div",{className:"perf-field-grid",children:[w&&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:P=>ae({orders_per_window:Number(P.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:P=>ae({order_window_ms:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order dispatch"}),l.jsxs("select",{value:r.rate.dispatch??"smooth",onChange:P=>ae({dispatch:P.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(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:P=>ae({fills_per_window:Number(P.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:P=>ae({fill_window_ms:Number(P.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:P=>ae({fill_ratio:Number(P.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:P=>ae({fill_dispatch:P.target.value}),children:[l.jsx("option",{value:"smooth",children:"smooth (evenly spaced)"}),l.jsx("option",{value:"burst",children:"burst (all at window start)"})]})]})]})]}),l.jsxs("div",{className:"perf-hint",children:[w&&l.jsxs(l.Fragment,{children:["≈ ",qp(r.rate.orders_per_window,r.rate.order_window_ms).toLocaleString()," orders/s"]}),w&&_&&" · ",_&&l.jsxs(l.Fragment,{children:["≈ ",qp(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:P=>ee({duration:Number(P.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:P=>ee({max_orders:Number(P.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:P=>ee({scenario_timeout_ms:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"On saturation"}),l.jsxs("select",{value:r.test.on_saturation,onChange:P=>ee({on_saturation:P.target.value}),children:[l.jsx("option",{value:"pause",children:"pause (preserves latency accuracy)"}),l.jsx("option",{value:"drop_oldest",children:"drop_oldest"})]})]})]}),w&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:r.test.record_execs,onChange:P=>ee({record_execs:P.target.checked})}),"Record per-exec detail — one CSV row per ack/fill with latency + inter-report gap (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."]})]}),w&&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:S.enabled,onChange:P=>me({enabled:P.target.checked})}),"Amend working orders — price-only OrderCancelReplaceRequest fired every nth fill"]}),S.enabled&&l.jsxs(l.Fragment,{children:[l.jsxs("div",{className:"perf-field-grid",children:[l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Every n fills"}),l.jsx("input",{type:"number",min:1,value:S.every_n_fills,onChange:P=>me({every_n_fills:Number(P.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:S.amend_ratio,onChange:P=>me({amend_ratio:Number(P.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:S.max_per_order,onChange:P=>me({max_per_order:Number(P.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:S.price_offset,onChange:P=>me({price_offset:Number(P.target.value)})})]})]}),_&&l.jsxs("label",{className:"checkbox-label",children:[l.jsx("input",{type:"checkbox",checked:S.pending_replace,onChange:P=>me({pending_replace:P.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)."}),_&&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."]})]})]}),w&&l.jsxs("div",{className:`perf-form-section ${k?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Order payload",k&&l.jsx("span",{className:"perf-layer-badge",children:"values for template"})]}),k&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Order template ",l.jsx("strong",{children:W})," 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:P=>i(P.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:P=>de({side:P.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 ${k?"perf-field-overridden":""}`,children:[l.jsxs("span",{children:["Order type ",k&&l.jsx("em",{className:"perf-field-tag",children:"from template"})]}),l.jsxs("select",{value:r.payload.order.ord_type,disabled:k,onChange:P=>de({ord_type:P.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:P=>de({quantity_min:Number(P.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:P=>de({quantity_max:Number(P.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:P=>de({price_min:Number(P.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:P=>de({price_max:Number(P.target.value)})})]})]})]}),w&&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:P=>R({auto_expiry:P.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:P=>R({expiry_offset:Number(P.target.value)})}),l.jsxs("select",{value:r.payload.expiry_unit??"days",onChange:P=>R({expiry_unit:P.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"})]})]}),ie.map((P,B)=>l.jsxs("div",{className:"perf-field-row",children:[l.jsx("input",{style:{width:72},placeholder:"tag",value:P.tag,onChange:it=>F(B,{tag:it.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:P.value,onChange:it=>F(B,{value:it.target.value})}),l.jsx("button",{className:"btn btn-sm btn-ghost btn-danger-ghost",onClick:()=>z(ie.filter((it,Er)=>Er!==B)),children:"✕"})]},B)),l.jsx("div",{className:"perf-field-row",children:l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>z([...ie,{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."]})]}),_&&l.jsxs("div",{className:`perf-form-section ${$?"perf-section-layered":""}`,children:[l.jsxs("div",{className:"perf-section-title",children:["Fill payload",$&&l.jsx("span",{className:"perf-layer-badge",children:"behavior for template"})]}),$&&l.jsxs("div",{className:"perf-hint perf-layer-note",children:["Exec template ",l.jsx("strong",{children:G})," 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:P=>Oe({fill_type:P.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"}),l.jsx("input",{type:"number",min:1,value:r.payload.fill.fills_per_order,onChange:P=>Oe({fills_per_order:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.payload.fill.send_ack,onChange:P=>Oe({send_ack:P.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:P=>Oe({partial_fill_pct_min:Number(P.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:P=>Oe({partial_fill_pct_max:Number(P.target.value)})})]})]})]})]}),(w||_)&&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:[w&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Order template"}),l.jsxs("select",{value:r.payload.order_template_id??"",onChange:P=>R({order_template_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),L.map(P=>l.jsx("option",{value:P.id,children:P.name},P.id))]})]}),_&&l.jsxs("label",{className:"perf-field",children:[l.jsx("span",{children:"Exec report template"}),l.jsxs("select",{value:r.payload.exec_template_id??"",onChange:P=>R({exec_template_id:P.target.value||null}),children:[l.jsx("option",{value:"",children:"— built-in —"}),H.map(P=>l.jsx("option",{value:P.id,children:P.name},P.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:()=>s(P=>!P),children:[o?"▾":"▸"," Advanced"]}),o&&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:P=>J({correlation_tag:Number(P.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:P=>J({exec_id_tag:Number(P.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:P=>ee({max_pending:Number(P.target.value)})})]}),l.jsxs("label",{className:"perf-field perf-field-check",children:[l.jsx("input",{type:"checkbox",checked:r.rate.allow_burst,onChange:P=>ae({allow_burst:P.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:P=>ae({max_burst_multiplier:Number(P.target.value)})})]})]})]}),l.jsxs("div",{className:"perf-form-actions",children:[T?l.jsx("span",{className:"perf-not-ready",children:T}):I&&!(N&&A)?l.jsxs("span",{className:"perf-not-ready",children:["Selected session",w&&_?"s":""," must be LOGGED_ON before starting."]}):null,l.jsx("button",{className:"btn btn-primary",onClick:M,disabled:!C,title:I?T||(N&&A?void 0:"Session not logged on yet"):"Select the required session(s)",children:f?"Starting…":"▶ Start run"})]})]})}function Yp({status:e}){const t=e==="LOGGED_ON";return l.jsxs("span",{className:`perf-status-chip ${t?"ok":"warn"}`,children:[t?"● ":"○ ",e]})}const Xp=600,oW=5e3,sW=new Set(["completed","stopped","error"]);function lW(e){const[t,r]=h.useState(null),[n,a]=h.useState([]),[i,o]=h.useState(!1);return h.useEffect(()=>{if(r(null),a([]),o(!1),!e)return;let s=!1,c=null,u,f=0,d=!1;async function m(){if(s||d||!e)return;let v;try{({ticket:v}=await Z.perf.runs.ticket(e))}catch{p();return}if(s)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=()=>{s||(f=0,o(!0))},c.onmessage=b=>{let x;try{x=JSON.parse(b.data)}catch{return}r(x),a(w=>{var j,O,N,A,I,T,C,U,L;const _={t:x.elapsed_s,ops:((j=x.client)==null?void 0:j.ops_live)??0,fps:((O=x.venue)==null?void 0:O.fps_live)??0,aps:((N=x.client)==null?void 0:N.aps_live)??0,tps:(((A=x.client)==null?void 0:A.ops_live)??0)+(((I=x.venue)==null?void 0:I.fps_live)??0),p50:(((T=x.response_latency)==null?void 0:T.p50_us)??0)/1e3,p99:(((C=x.response_latency)==null?void 0:C.p99_us)??0)/1e3,fc50:(((U=x.fill_completion_latency)==null?void 0:U.p50_us)??0)/1e3,fc99:(((L=x.fill_completion_latency)==null?void 0:L.p99_us)??0)/1e3},S=[...w,_];return S.length>Xp?S.slice(-Xp):S}),sW.has(x.status)&&(d=!0,c==null||c.close())},c.onclose=()=>{s||(o(!1),d||p())},c.onerror=()=>{c==null||c.close()}}function p(){if(s||d)return;f+=1;const v=Math.min(oW,500*2**Math.min(f,4));u=setTimeout(m,v)}return m(),()=>{s=!0,clearTimeout(u),c==null||c.close()}},[e]),{latest:t,series:n,connected:i}}const Lw="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",s=await(r==="png"?n:a)(e,{backgroundColor:i,pixelRatio:2,quality:.95,filter:u=>!(u instanceof HTMLElement&&u.classList.contains(Lw))}),c=document.createElement("a");c.href=s,c.download=t,c.click()}function uW(e,t){const r=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");return`${e}-${r}.${t}`}const Pl=new Set(["completed","stopped","error"]);function vr(e){return e?(e/1e3).toFixed(3):"—"}function Fw(e,t){return t<=0?"—":(e/t).toFixed(2)}function Nl({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:vr(r[n])}),l.jsxs("span",{className:"perf-lat-lbl",children:[n.replace("_us","")," ms"]})]},n))})]})}function dW({runId:e}){var w,_;const{latest:t,series:r,connected:n}=lW(e),[a,i]=h.useState(null),[o,s]=h.useState(!1),c=h.useRef(null);h.useEffect(()=>{let S=!0;i(null);let j;const O=async()=>{try{const N=await Z.perf.runs.get(e);if(!S)return;i(N),Pl.has(N.status)&&clearInterval(j)}catch{}};return j=setInterval(O,2e3),O(),()=>{S=!1,clearInterval(j)}},[e]);const u=t?Pl.has(t.status):a?Pl.has(a.status):!1,f=!u;async function d(){try{await Z.perf.runs.stop(e)}catch{}}async function m(S){if(!(!c.current||o)){s(!0);try{const j=S==="png"?"png":"jpg";await cW(c.current,uW(`perf-${e.slice(0,8)}`,j),S)}catch(j){console.error("image export failed",j)}finally{s(!1)}}}const p=t==null?void 0:t.client,v=t==null?void 0:t.venue,y=t==null?void 0:t.errors,g=(y==null?void 0:y.saturated)||(t==null?void 0:t.status)==="saturated",b=t==null?void 0:t.rate_config,x=[];return p&&(x.push({label:"orders/s",value:Math.round(p.ops_live).toLocaleString()}),(b==null?void 0:b.orders_per_window)!=null&&(b==null?void 0:b.order_window_ms)!=null&&x.push({label:"order rate (cfg)",value:`${b.orders_per_window.toLocaleString()} / ${b.order_window_ms}ms`}),x.push({label:"orders sent",value:p.orders_sent.toLocaleString()},{label:"pending",value:p.pending.toLocaleString()},{label:"dropped",value:p.dropped.toLocaleString()}),(p.amends_sent>0||p.aps_live>0)&&(x.push({label:"amends/s",value:Math.round(p.aps_live).toLocaleString()},{label:"amends",value:p.amends_sent.toLocaleString()},{label:"replaces",value:p.replaces_received.toLocaleString()}),p.amends_lost>0&&x.push({label:"amends lost",value:p.amends_lost.toLocaleString()})),p.amend_rejects>0&&x.push({label:"amend rejects (35=9)",value:p.amend_rejects.toLocaleString()})),v&&(x.push({label:"fills/s",value:Math.round(v.fps_live).toLocaleString()}),(b==null?void 0:b.fills_per_window)!=null&&(b==null?void 0:b.fill_window_ms)!=null&&x.push({label:"fill rate (cfg)",value:`${b.fills_per_window.toLocaleString()} / ${b.fill_window_ms}ms`}),x.push({label:"fills",value:v.fills_sent.toLocaleString()},{label:"fills/order",value:Fw(v.fills_sent,v.orders_received-v.unfilled)},{label:"fill ratio",value:v.fill_ratio.toFixed(3)},{label:"recv",value:v.orders_received.toLocaleString()},{label:"acks",value:v.acks_sent.toLocaleString()},{label:"unfilled",value:v.unfilled.toLocaleString()}),v.amends_received>0&&x.push({label:"amends recv",value:v.amends_received.toLocaleString()}),v.amends_uncorrelated>0&&x.push({label:"amends no-corr",value:v.amends_uncorrelated.toLocaleString()})),y&&x.push({label:"lost",value:y.lost_timeout.toLocaleString()},{label:"rejected",value:y.rejected.toLocaleString()}),l.jsxs("div",{className:"perf-dashboard",ref:c,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 ${Lw}`,children:[f&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:d,children:"■ Stop"}),l.jsx("button",{className:"btn btn-sm btn-ghost",onClick:()=>Z.perf.runs.export(e,"messages"),children:"Export CSV"}),u&&l.jsx("button",{className:"btn btn-sm btn-ghost",title:"Per-exec detail (requires record_execs)",onClick:()=>Z.perf.runs.export(e,"execs"),children:"Execs 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:o,onClick:()=>m("png"),children:o?"…":"PNG"}),l.jsx("button",{className:"btn btn-sm btn-ghost",disabled:o,onClick:()=>m("jpeg"),children:o?"…":"JPG"})]})]}),g&&l.jsxs("div",{className:"perf-saturated-banner",children:["⚠ SATURATED — injection paused to protect latency measurement.",p&&l.jsxs(l.Fragment,{children:[" Pending ",p.pending.toLocaleString(),", dropped ",p.dropped.toLocaleString(),"."]})]}),(w=a==null?void 0:a.warnings)==null?void 0:w.map((S,j)=>l.jsxs("div",{className:"perf-warning-banner",children:["⚠ ",S]},j)),!t&&!u&&l.jsx("div",{className:"loading-msg",children:"Waiting for first snapshot…"}),x.length>0&&l.jsx("div",{className:"stat-cards",children:x.map(({label:S,value:j})=>l.jsxs("div",{className:"stat-card",children:[l.jsx("span",{className:"stat-card-value",children:j}),l.jsx("span",{className:"stat-card-label",children:S})]},S))}),t&&l.jsxs("div",{className:"perf-lat-row",children:[l.jsx(Nl,{title:"Response latency",sub:"order-2-ack",stats:t.response_latency}),l.jsx(Nl,{title:"Fill-completion latency",sub:"order-2-last-fill",stats:t.fill_completion_latency}),((_=t.amend_latency)==null?void 0:_.count)>0&&l.jsx(Nl,{title:"Amend latency",sub:"amend-2-replaced",stats:t.amend_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(Nn,{width:"100%",height:200,children:l.jsxs(Ti,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Zr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Kr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:S=>`${Math.round(S)}s`}),l.jsx(qr,{tick:{fontSize:11,fill:"var(--text2)"},allowDecimals:!1}),l.jsx(An,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:S=>`${Math.round(Number(S))}s`}),l.jsx(ga,{wrapperStyle:{fontSize:11}}),l.jsx(yt,{type:"monotone",dataKey:"ops",name:"orders/s",stroke:"#00b4c8",dot:!1,isAnimationActive:!1}),l.jsx(yt,{type:"monotone",dataKey:"fps",name:"fills/s",stroke:"#a78bfa",dot:!1,isAnimationActive:!1}),((p==null?void 0:p.amends_sent)??0)>0&&l.jsx(yt,{type:"monotone",dataKey:"aps",name:"amends/s",stroke:"#c084fc",dot:!1,isAnimationActive:!1}),l.jsx(yt,{type:"monotone",dataKey:"tps",name:"total msgs/s",stroke:"#fbbf24",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(Nn,{width:"100%",height:200,children:l.jsxs(Ti,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Zr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Kr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:S=>`${Math.round(S)}s`}),l.jsx(qr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(An,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:S=>`${Math.round(Number(S))}s`}),l.jsx(ga,{wrapperStyle:{fontSize:11}}),l.jsx(yt,{type:"monotone",dataKey:"p50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(yt,{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(Nn,{width:"100%",height:200,children:l.jsxs(Ti,{data:r,margin:{top:4,right:16,bottom:4,left:8},children:[l.jsx(Zr,{strokeDasharray:"3 3",stroke:"var(--border)"}),l.jsx(Kr,{dataKey:"t",tick:{fontSize:11,fill:"var(--text2)"},tickFormatter:S=>`${Math.round(S)}s`}),l.jsx(qr,{tick:{fontSize:11,fill:"var(--text2)"}}),l.jsx(An,{contentStyle:{background:"var(--bg3)",border:"1px solid var(--border)",fontSize:12},labelStyle:{color:"var(--text)"},labelFormatter:S=>`${Math.round(Number(S))}s`}),l.jsx(ga,{wrapperStyle:{fontSize:11}}),l.jsx(yt,{type:"monotone",dataKey:"fc50",name:"p50",stroke:"#4ade80",dot:!1,isAnimationActive:!1}),l.jsx(yt,{type:"monotone",dataKey:"fc99",name:"p99",stroke:"#f87171",dot:!1,isAnimationActive:!1})]})})})]}),u&&(a==null?void 0:a.summary)&&l.jsx(fW,{status:a})]})}function fW({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)",Fw(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)",`${vr(t.response_latency.p50_us)} / ${vr(t.response_latency.p99_us)}`],["Response max (ms)",vr(t.response_latency.max_us)],["Fill-completion p50 / p99 (ms)",`${vr(t.fill_completion_latency.p50_us)} / ${vr(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",`${t.amends_received??0} / ${t.amends_uncorrelated??0}`]),t.amend_latency&&r.push(["Amend p50 / p99 (ms)",`${vr(t.amend_latency.p50_us)} / ${vr(t.amend_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 hW=new Set(["completed","stopped","error"]);function mW(e){if(!e)return"—";try{return new Date(e*1e3).toLocaleTimeString()}catch{return"—"}}function pW({activeRunId:e,onSelect:t,refreshKey:r}){const[n,a]=h.useState([]);h.useEffect(()=>{let o=!0;const s=()=>Z.perf.runs.list().then(u=>{o&&a(u)}).catch(()=>{});s();const c=setInterval(s,2e3);return()=>{o=!1,clearInterval(c)}},[r]);async function i(o,s){o.stopPropagation();try{await Z.perf.runs.stop(s)}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(o=>l.jsxs("tr",{className:o.run_id===e?"perf-run-active":"",onClick:()=>t(o.run_id),children:[l.jsx("td",{children:o.name}),l.jsx("td",{children:o.mode}),l.jsx("td",{children:l.jsx("span",{className:`badge perf-state-${o.status}`,children:o.status})}),l.jsx("td",{children:mW(o.started_at)}),l.jsxs("td",{className:"mono",children:[Math.round(o.elapsed_s),"s"]}),l.jsx("td",{className:"mono",children:o.summary?o.summary.orders_sent.toLocaleString():"—"}),l.jsx("td",{className:"mono",children:o.summary?o.summary.fill_ratio.toFixed(2):"—"}),l.jsx("td",{className:"perf-runlist-actions",children:!hW.has(o.status)&&l.jsx("button",{className:"btn btn-sm btn-danger",onClick:s=>i(s,o.run_id),children:"Stop"})})]},o.run_id))})]})}const Cl=200,On=(e,t,r=120)=>({field:e,headerName:t,width:r,type:"numericColumn",valueFormatter:n=>n.value==null?"":Number(n.value).toLocaleString()}),Ac=(e,t)=>({field:e,headerName:t,width:150,type:"numericColumn",valueFormatter:r=>r.value==null?"":`${Math.round(Number(r.value)).toLocaleString()}`}),vW=[{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},On("qty","Qty",90),On("price","Price",100),Ac("response_latency_us","Resp µs"),Ac("fill_latency_us","Fill µs"),On("fill_qty","Fill qty",100),On("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}],gW=[{field:"scenario_name",headerName:"Scenario",flex:1,minWidth:160},Ac("latency_us","Latency µs"),On("msg_count","Msgs",90),On("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 yW({runId:e}){const t=V(g=>g.theme),[r,n]=h.useState("messages"),[a,i]=h.useState(0),[o,s]=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:Cl,offset:a*Cl}).then(x=>{g&&(s(x.items),u(x.total))}).catch(()=>{g&&(s([]),u(0))}).finally(()=>{g&&d(!1)}),()=>{g=!1}},[e,r,a]);const m=h.useMemo(()=>r==="messages"?vW:gW,[r]),p=h.useMemo(()=>({sortable:!0,filter:!0,resizable:!0}),[]),v=Math.max(0,Math.ceil(c/Cl)-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(kc,{rowData:o,columnDefs:m,defaultColDef:p,animateRows:!1,suppressCellFocus:!0})})]})}function bW(){const{theme:e,toggleTheme:t,branding:r,setSessions:n}=V(),a=Gs(),i=r!=null&&r.prefix?`${r.prefix} FIXture`:"FIXture";Pv(),h.useEffect(()=>{Z.sessions.list().then(n).catch(()=>{})},[]);const[o,s]=h.useState("configure"),[c,u]=h.useState(null),[f,d]=h.useState(0);function m(y){u(y),d(g=>g+1),s("live")}function p(y){u(y),s("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(Yr,{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${o===y.id?" active":""}`,disabled:y.disabled,onClick:()=>s(y.id),children:y.label},y.id))}),l.jsxs("div",{className:"perf-body",children:[o==="configure"&&l.jsx("div",{className:"perf-pane perf-pane-form",children:l.jsx(iW,{onStarted:m})}),o==="live"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(dW,{runId:c},c)}),o==="results"&&c&&l.jsx("div",{className:"perf-pane",children:l.jsx(yW,{runId:c},c)}),o==="history"&&l.jsx("div",{className:"perf-pane",children:l.jsx(pW,{activeRunId:c,onSelect:p,refreshKey:f})})]})]})}function xW(){return V(t=>t.token)?l.jsx(hv,{}):l.jsx(Ri,{to:"/login",replace:!0})}function wW(){const e=V(t=>t.user);return e?e.role!=="platform_admin"?l.jsx(Ri,{to:"/",replace:!0}):l.jsx(hv,{}):l.jsx(Ri,{to:"/login",replace:!0})}function jW(){const e=V(t=>t.setBranding);h.useEffect(()=>{fetch("/api/branding").then(t=>t.json()).then(t=>{e(t),Rw(t)}).catch(()=>{})},[])}function SW(){const e=on(),t=V(r=>r.theme);return jW(),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(Kj,{children:[l.jsx(Lt,{path:"/setup",element:l.jsx(Vz,{})}),l.jsx(Lt,{path:"/login",element:l.jsx(Xz,{})}),l.jsx(Lt,{path:"/register",element:l.jsx(Jz,{})}),l.jsxs(Lt,{element:l.jsx(xW,{}),children:[l.jsx(Lt,{path:"/",element:l.jsx(Yz,{})}),l.jsxs(Lt,{element:l.jsx(wW,{}),children:[l.jsx(Lt,{path:"/admin/users",element:l.jsx(nW,{})}),l.jsx(Lt,{path:"/perf",element:l.jsx(bW,{})})]})]}),l.jsx(Lt,{path:"*",element:l.jsx(Ri,{to:"/",replace:!0})})]})}El.createRoot(document.getElementById("root")).render(l.jsx(Vp.StrictMode,{children:l.jsx(gS,{children:l.jsx(SW,{})})}));
|
|
@@ -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-BFPDQdQb.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/ag-grid-_QKprVdm.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-BwQf-cei.css">
|
|
11
11
|
</head>
|
|
@@ -63,9 +63,9 @@ fixture/fix_specs/FIX44.xml
|
|
|
63
63
|
fixture/static/favicon.svg
|
|
64
64
|
fixture/static/index.html
|
|
65
65
|
fixture/static/assets/ag-grid-_QKprVdm.js
|
|
66
|
+
fixture/static/assets/index-BFPDQdQb.js
|
|
66
67
|
fixture/static/assets/index-BwQf-cei.css
|
|
67
68
|
fixture/static/assets/index-CyNOPa0n.js
|
|
68
|
-
fixture/static/assets/index-DrmyYeG0.js
|
|
69
69
|
fixture/static/assets/react-vendor-2eF0YfZT.js
|
|
70
70
|
fixture/ui/__init__.py
|
|
71
71
|
fixtureqa.egg-info/PKG-INFO
|
|
@@ -727,3 +727,57 @@ async def test_venue_amend_without_pending_replace():
|
|
|
727
727
|
venue_msgs = [f for sid, f in run._sm.sent if sid == "v"]
|
|
728
728
|
assert [f.get(150) for f in venue_msgs] == ["5"]
|
|
729
729
|
assert run.stats.amends_received == 1
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
async def test_amend_reject_counts_and_releases_order():
|
|
733
|
+
# A 35=9 OrderCancelReject ends the in-flight amend: counted as a reject
|
|
734
|
+
# (not lost), chain tip NOT promoted, and the order still finalizes.
|
|
735
|
+
cfg = _cfg(amend=AmendConfig(enabled=True, every_n_fills=1, max_per_order=1))
|
|
736
|
+
run = _run(cfg)
|
|
737
|
+
run._register_pending(_amend_meta("A3"))
|
|
738
|
+
entry = run._pending["A3"]
|
|
739
|
+
run._on_exec_report({376: "A3", 25116: "F1", 14: "50", 38: "100",
|
|
740
|
+
150: "1", 39: "1"}, time.perf_counter_ns())
|
|
741
|
+
await asyncio.sleep(0.01)
|
|
742
|
+
assert run.stats.amends_sent == 1 and entry["amend_inflight"]
|
|
743
|
+
|
|
744
|
+
run._on_cancel_reject({376: "A3", 11: entry["pending_clordid"], 41: "PCL-ORIG"})
|
|
745
|
+
assert run.stats.amend_rejects == 1
|
|
746
|
+
assert not entry["amend_inflight"]
|
|
747
|
+
assert entry["clordid"] == "PCL-ORIG" # chain tip unchanged
|
|
748
|
+
assert any("OrderCancelReject" in w for w in run.warnings)
|
|
749
|
+
|
|
750
|
+
run._on_exec_report({376: "A3", 25116: "F2", 14: "100", 38: "100",
|
|
751
|
+
150: "2", 39: "2"}, time.perf_counter_ns())
|
|
752
|
+
assert run.stats.completions == 1
|
|
753
|
+
assert "A3" not in run._pending # finalized normally
|
|
754
|
+
assert run.stats.amends_lost == 0 # rejected ≠ lost
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
async def test_amend_reject_after_completion_finalizes():
|
|
758
|
+
# Reject arriving after the final fill (entry deferred for the amend) must
|
|
759
|
+
# still release and finalize the order as filled.
|
|
760
|
+
cfg = _cfg(amend=AmendConfig(enabled=True, every_n_fills=1, max_per_order=1))
|
|
761
|
+
run = _run(cfg)
|
|
762
|
+
run._register_pending(_amend_meta("A4"))
|
|
763
|
+
run._on_exec_report({376: "A4", 25116: "F1", 14: "50", 38: "100",
|
|
764
|
+
150: "1", 39: "1"}, time.perf_counter_ns())
|
|
765
|
+
await asyncio.sleep(0.01)
|
|
766
|
+
run._on_exec_report({376: "A4", 25116: "F2", 14: "100", 38: "100",
|
|
767
|
+
150: "2", 39: "2"}, time.perf_counter_ns())
|
|
768
|
+
assert "A4" in run._pending # held for the amend
|
|
769
|
+
run._on_cancel_reject({376: "A4"})
|
|
770
|
+
assert run.stats.amend_rejects == 1
|
|
771
|
+
assert "A4" not in run._pending
|
|
772
|
+
assert [r["status"] for r in run._writer.msgs
|
|
773
|
+
if r["corr_id"] == "A4"] == ["filled"]
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
async def test_venue_amend_without_corr_counts_uncorrelated():
|
|
777
|
+
run = _run(_cfg(amend=AmendConfig(enabled=True)))
|
|
778
|
+
await run._handle_amend({35: "G", 11: "NEW-3", 41: "OLD-3",
|
|
779
|
+
38: "100", 44: "10.05"}) # no 376
|
|
780
|
+
assert run.stats.amends_received == 1
|
|
781
|
+
assert run.stats.amends_uncorrelated == 1
|
|
782
|
+
assert not [f for sid, f in run._sm.sent if sid == "v"] # no reply possible
|
|
783
|
+
assert any("without correlation tag" in w for w in run.warnings)
|
|
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
|