fixcore-engine 0.1.2__tar.gz → 0.1.4__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.
- {fixcore_engine-0.1.2/fixcore_engine.egg-info → fixcore_engine-0.1.4}/PKG-INFO +1 -1
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/gui_ui/app.js +113 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/gui_ui/index.html +11 -1
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/gui_ui/style.css +74 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/transport/initiator.py +6 -8
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4/fixcore_engine.egg-info}/PKG-INFO +1 -1
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/pyproject.toml +1 -1
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/LICENSE +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/README.md +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/__init__.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/application.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/gui.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/gui_ui/fixcore_logo.svg +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/log/__init__.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/log/base.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/log/factory.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/log/file_log.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/log/screen.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/message/__init__.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/message/cracker.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/message/data_dictionary.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/message/exceptions.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/message/field.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/message/message.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/session/__init__.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/session/session.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/session/session_id.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/session/session_settings.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/session/state.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/store/__init__.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/store/base.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/store/factory.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/store/file_store.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/store/memory.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/transport/__init__.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/transport/acceptor.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore/transport/framer.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore_engine.egg-info/SOURCES.txt +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore_engine.egg-info/dependency_links.txt +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore_engine.egg-info/entry_points.txt +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore_engine.egg-info/requires.txt +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/fixcore_engine.egg-info/top_level.txt +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/setup.cfg +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_cracker.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_data_dictionary.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_file_log.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_file_store.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_framer.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_integration.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_message.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_session.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_session_id.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_store.py +0 -0
- {fixcore_engine-0.1.2 → fixcore_engine-0.1.4}/tests/test_transport.py +0 -0
|
@@ -84,6 +84,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
84
84
|
updateClordidPreview();
|
|
85
85
|
});
|
|
86
86
|
$id('inp-clordid-prefix').addEventListener('input', updateClordidPreview);
|
|
87
|
+
|
|
88
|
+
$id('btn-close-detail').addEventListener('click', closeMsgDetail);
|
|
89
|
+
$id('log-entries').addEventListener('click', e => {
|
|
90
|
+
const row = e.target.closest('.log-entry');
|
|
91
|
+
if (!row || !row._logEntry) return;
|
|
92
|
+
if (row === selectedLogRow) { closeMsgDetail(); return; }
|
|
93
|
+
showMsgDetail(row, row._logEntry);
|
|
94
|
+
});
|
|
87
95
|
});
|
|
88
96
|
|
|
89
97
|
// ── Polling (seq nums + state) ────────────────────────────────────────────
|
|
@@ -550,6 +558,96 @@ $id('paste-fix-input').addEventListener('keydown', e => {
|
|
|
550
558
|
}
|
|
551
559
|
});
|
|
552
560
|
|
|
561
|
+
// ── FIX tag reference ─────────────────────────────────────────────────────
|
|
562
|
+
const FIX_TAG_NAMES = {
|
|
563
|
+
1:'Account', 6:'AvgPx', 7:'BeginSeqNo', 8:'BeginString', 9:'BodyLength',
|
|
564
|
+
10:'CheckSum', 11:'ClOrdID', 14:'CumQty', 15:'Currency', 16:'EndSeqNo',
|
|
565
|
+
31:'LastPx', 32:'LastQty',
|
|
566
|
+
17:'ExecID', 18:'ExecInst', 20:'ExecTransType', 21:'HandlInst',
|
|
567
|
+
34:'MsgSeqNum', 35:'MsgType', 36:'NewSeqNo', 37:'OrderID', 38:'OrderQty',
|
|
568
|
+
39:'OrdStatus', 40:'OrdType', 41:'OrigClOrdID', 43:'PossDupFlag',
|
|
569
|
+
44:'Price', 45:'RefSeqNum', 49:'SenderCompID', 50:'SenderSubID',
|
|
570
|
+
52:'SendingTime', 54:'Side', 55:'Symbol', 56:'TargetCompID',
|
|
571
|
+
57:'TargetSubID', 58:'Text', 60:'TransactTime', 97:'PossResend',
|
|
572
|
+
98:'EncryptMethod', 108:'HeartBtInt', 110:'MinQty', 111:'MaxFloor',
|
|
573
|
+
112:'TestReqID', 114:'LocateReqd', 122:'OrigSendingTime', 123:'GapFillFlag',
|
|
574
|
+
126:'ExpireTime', 131:'QuoteReqID', 140:'PrevClosePx', 146:'NoRelatedSym',
|
|
575
|
+
150:'ExecType', 151:'LeavesQty', 167:'SecurityType', 200:'MaturityMonthYear',
|
|
576
|
+
202:'StrikePrice', 207:'SecurityExchange', 262:'MDReqID',
|
|
577
|
+
263:'SubscriptionRequestType', 264:'MarketDepth', 267:'NoMDEntryTypes',
|
|
578
|
+
268:'NoMDEntries', 269:'MDEntryType', 270:'MDEntryPx', 271:'MDEntrySize',
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const FIX_ENUM_VALUES = {
|
|
582
|
+
35: {'0':'Heartbeat','1':'TestRequest','2':'ResendRequest','3':'Reject',
|
|
583
|
+
'4':'SequenceReset','5':'Logout','8':'ExecutionReport',
|
|
584
|
+
'9':'OrderCancelReject','A':'Logon','D':'NewOrderSingle',
|
|
585
|
+
'F':'OrderCancelRequest','G':'OrderCancelReplaceRequest',
|
|
586
|
+
'H':'OrderStatusRequest','V':'MarketDataRequest',
|
|
587
|
+
'W':'MarketDataSnapshot','X':'MarketDataIncremental','Y':'MarketDataRequestReject'},
|
|
588
|
+
39: {'0':'New','1':'PartiallyFilled','2':'Filled','3':'DoneForDay',
|
|
589
|
+
'4':'Canceled','5':'Replaced','6':'PendingCancel','7':'Stopped',
|
|
590
|
+
'8':'Rejected','9':'Suspended','A':'PendingNew','C':'Expired'},
|
|
591
|
+
40: {'1':'Market','2':'Limit','3':'Stop','4':'StopLimit','5':'MarketOnClose'},
|
|
592
|
+
54: {'1':'Buy','2':'Sell','3':'BuyMinus','4':'SellPlus','5':'SellShort','6':'SellShortExempt'},
|
|
593
|
+
98: {'0':'None','1':'Value','2':'DES','3':'PKCS','4':'PGP','5':'PCSDSS','6':'PEM'},
|
|
594
|
+
150:{'0':'New','1':'PartialFill','2':'Fill','3':'DoneForDay','4':'Canceled',
|
|
595
|
+
'5':'Replace','6':'PendingCancel','7':'Stopped','8':'Rejected',
|
|
596
|
+
'D':'Restated','E':'Trade','F':'TradeCorrect','G':'TradeCancel','H':'OrderStatus'},
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
function fixTagName(tag) { return FIX_TAG_NAMES[parseInt(tag)] || ''; }
|
|
600
|
+
function fixEnumVal(tag, val) {
|
|
601
|
+
const map = FIX_ENUM_VALUES[parseInt(tag)];
|
|
602
|
+
return map ? (map[val] || val) : val;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ── Message detail panel ──────────────────────────────────────────────────
|
|
606
|
+
let selectedLogRow = null;
|
|
607
|
+
|
|
608
|
+
function showMsgDetail(row, entry) {
|
|
609
|
+
if (selectedLogRow) selectedLogRow.classList.remove('selected');
|
|
610
|
+
selectedLogRow = row;
|
|
611
|
+
row.classList.add('selected');
|
|
612
|
+
|
|
613
|
+
const content = $id('msg-detail-content');
|
|
614
|
+
content.innerHTML = '';
|
|
615
|
+
|
|
616
|
+
// Meta info
|
|
617
|
+
const meta = document.createElement('div');
|
|
618
|
+
meta.className = 'msg-detail-meta';
|
|
619
|
+
meta.innerHTML = `<strong>${dirArrow(entry.dir)} ${entry.dir.toUpperCase()}</strong>
|
|
620
|
+
${escHtml(entry.time)}
|
|
621
|
+
<span title="${escHtml(entry.sid)}">${escHtml(shortSid(entry.sid))}</span>`;
|
|
622
|
+
content.appendChild(meta);
|
|
623
|
+
|
|
624
|
+
// Parse fields
|
|
625
|
+
entry.raw.split('|').filter(Boolean).forEach(part => {
|
|
626
|
+
const eq = part.indexOf('=');
|
|
627
|
+
if (eq < 1) return;
|
|
628
|
+
const tag = part.slice(0, eq).trim();
|
|
629
|
+
const val = part.slice(eq + 1);
|
|
630
|
+
const name = fixTagName(tag);
|
|
631
|
+
const display = fixEnumVal(tag, val);
|
|
632
|
+
|
|
633
|
+
const row = document.createElement('div');
|
|
634
|
+
row.className = 'msg-detail-row';
|
|
635
|
+
row.innerHTML =
|
|
636
|
+
`<span class="detail-tag">${escHtml(tag)}</span>` +
|
|
637
|
+
`<span class="detail-name" title="${escHtml(name)}">${escHtml(name)}</span>` +
|
|
638
|
+
`<span class="detail-val" title="${escHtml(val)}">${escHtml(display)}</span>`;
|
|
639
|
+
content.appendChild(row);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
$id('msg-detail-panel').classList.remove('hidden');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function closeMsgDetail() {
|
|
646
|
+
if (selectedLogRow) { selectedLogRow.classList.remove('selected'); selectedLogRow = null; }
|
|
647
|
+
$id('msg-detail-panel').classList.add('hidden');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
|
|
553
651
|
// ── Message log ───────────────────────────────────────────────────────────
|
|
554
652
|
const LOG_STORAGE_KEY = 'fixcore_log';
|
|
555
653
|
|
|
@@ -580,6 +678,7 @@ function appendLogRow(entry) {
|
|
|
580
678
|
const row = document.createElement('div');
|
|
581
679
|
row.className = `log-entry ${entry.dir}`;
|
|
582
680
|
row.dataset.dir = entry.dir;
|
|
681
|
+
row._logEntry = entry;
|
|
583
682
|
row.innerHTML = `
|
|
584
683
|
<span class="log-time">${escHtml(entry.time)}</span>
|
|
585
684
|
<span class="log-dir">${dirArrow(entry.dir)}</span>
|
|
@@ -605,9 +704,21 @@ function shortSid(sid) {
|
|
|
605
704
|
: sid.slice(0, 12);
|
|
606
705
|
}
|
|
607
706
|
|
|
707
|
+
const SESSION_MSG_TYPES = new Set(['0','1','2','3','4','5','A']);
|
|
708
|
+
|
|
709
|
+
function getMsgType(raw) {
|
|
710
|
+
const m = raw.match(/\b35=([^|]+)/);
|
|
711
|
+
return m ? m[1].trim() : null;
|
|
712
|
+
}
|
|
713
|
+
|
|
608
714
|
function matchesFilter(entry) {
|
|
609
715
|
if (activeSid && entry.sid !== activeSid) return false;
|
|
610
716
|
if (logFilter === 'all') return true;
|
|
717
|
+
if (logFilter === 'app') {
|
|
718
|
+
if (entry.dir === 'evt') return false;
|
|
719
|
+
const mt = getMsgType(entry.raw);
|
|
720
|
+
return mt !== null && !SESSION_MSG_TYPES.has(mt);
|
|
721
|
+
}
|
|
611
722
|
if (logFilter === 'in') return entry.dir === 'in';
|
|
612
723
|
if (logFilter === 'out') return entry.dir === 'out';
|
|
613
724
|
if (logFilter === 'event') return entry.dir === 'evt';
|
|
@@ -634,6 +745,7 @@ $id('btn-clear-log').addEventListener('click', () => {
|
|
|
634
745
|
logEntries = [];
|
|
635
746
|
localStorage.removeItem(LOG_STORAGE_KEY);
|
|
636
747
|
$id('log-entries').innerHTML = '';
|
|
748
|
+
closeMsgDetail();
|
|
637
749
|
});
|
|
638
750
|
|
|
639
751
|
// ── Keyboard shortcuts ────────────────────────────────────────────────────
|
|
@@ -642,6 +754,7 @@ document.addEventListener('keydown', e => {
|
|
|
642
754
|
$id('modal-overlay').classList.add('hidden');
|
|
643
755
|
$id('paste-modal-overlay').classList.add('hidden');
|
|
644
756
|
editMode = false; editOldKey = null;
|
|
757
|
+
closeMsgDetail();
|
|
645
758
|
}
|
|
646
759
|
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
|
|
647
760
|
e.preventDefault();
|
|
@@ -101,13 +101,23 @@
|
|
|
101
101
|
<span class="panel-title">Message Log</span>
|
|
102
102
|
<div class="log-filters">
|
|
103
103
|
<button class="filter-btn active" data-filter="all">All</button>
|
|
104
|
+
<button class="filter-btn" data-filter="app">App</button>
|
|
104
105
|
<button class="filter-btn" data-filter="out">▶ Out</button>
|
|
105
106
|
<button class="filter-btn" data-filter="in">◀ In</button>
|
|
106
107
|
<button class="filter-btn" data-filter="event">Events</button>
|
|
107
108
|
</div>
|
|
108
109
|
<button class="btn btn-secondary btn-sm" id="btn-clear-log">Clear</button>
|
|
109
110
|
</div>
|
|
110
|
-
<div
|
|
111
|
+
<div class="log-body">
|
|
112
|
+
<div id="log-entries" class="log-entries"></div>
|
|
113
|
+
<aside id="msg-detail-panel" class="msg-detail-panel hidden">
|
|
114
|
+
<div class="msg-detail-header">
|
|
115
|
+
<span class="msg-detail-title">Message Detail</span>
|
|
116
|
+
<button id="btn-close-detail" class="btn-close-detail">×</button>
|
|
117
|
+
</div>
|
|
118
|
+
<div id="msg-detail-content" class="msg-detail-content"></div>
|
|
119
|
+
</aside>
|
|
120
|
+
</div>
|
|
111
121
|
</section>
|
|
112
122
|
</main>
|
|
113
123
|
</div>
|
|
@@ -315,6 +315,12 @@ body {
|
|
|
315
315
|
border-color: var(--accent);
|
|
316
316
|
color: #fff;
|
|
317
317
|
}
|
|
318
|
+
.log-body {
|
|
319
|
+
display: flex;
|
|
320
|
+
flex: 1;
|
|
321
|
+
gap: 0;
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
}
|
|
318
324
|
.log-entries {
|
|
319
325
|
flex: 1;
|
|
320
326
|
overflow-y: auto;
|
|
@@ -324,6 +330,7 @@ body {
|
|
|
324
330
|
border: 1px solid var(--border);
|
|
325
331
|
border-radius: var(--radius);
|
|
326
332
|
padding: 6px 0;
|
|
333
|
+
min-width: 0;
|
|
327
334
|
}
|
|
328
335
|
.log-entry {
|
|
329
336
|
display: flex;
|
|
@@ -331,8 +338,10 @@ body {
|
|
|
331
338
|
padding: 2px 10px;
|
|
332
339
|
border-bottom: 1px solid transparent;
|
|
333
340
|
line-height: 1.5;
|
|
341
|
+
cursor: pointer;
|
|
334
342
|
}
|
|
335
343
|
.log-entry:hover { background: var(--bg2); }
|
|
344
|
+
.log-entry.selected { background: var(--bg3); outline: 1px solid var(--accent); }
|
|
336
345
|
.log-entry.in .log-dir { color: var(--in-color); }
|
|
337
346
|
.log-entry.out .log-dir { color: var(--out-color); }
|
|
338
347
|
.log-entry.evt .log-dir { color: var(--event-color); }
|
|
@@ -342,6 +351,71 @@ body {
|
|
|
342
351
|
.log-sid { color: var(--text-dim); flex-shrink: 0; max-width: 120px; overflow: hidden; text-overflow: ellipsis; }
|
|
343
352
|
.log-msg { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
344
353
|
|
|
354
|
+
/* ── Message detail panel ─────────────────────────────────────────────── */
|
|
355
|
+
.msg-detail-panel {
|
|
356
|
+
width: 320px;
|
|
357
|
+
flex-shrink: 0;
|
|
358
|
+
border-left: 1px solid var(--border);
|
|
359
|
+
display: flex;
|
|
360
|
+
flex-direction: column;
|
|
361
|
+
overflow: hidden;
|
|
362
|
+
background: var(--bg);
|
|
363
|
+
margin-left: 8px;
|
|
364
|
+
border-radius: var(--radius);
|
|
365
|
+
border: 1px solid var(--border);
|
|
366
|
+
}
|
|
367
|
+
.msg-detail-header {
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
justify-content: space-between;
|
|
371
|
+
padding: 6px 10px;
|
|
372
|
+
border-bottom: 1px solid var(--border);
|
|
373
|
+
flex-shrink: 0;
|
|
374
|
+
background: var(--bg2);
|
|
375
|
+
}
|
|
376
|
+
.msg-detail-title {
|
|
377
|
+
font-size: 11px;
|
|
378
|
+
font-weight: 600;
|
|
379
|
+
text-transform: uppercase;
|
|
380
|
+
letter-spacing: .08em;
|
|
381
|
+
color: var(--text-dim);
|
|
382
|
+
}
|
|
383
|
+
.btn-close-detail {
|
|
384
|
+
background: none;
|
|
385
|
+
border: none;
|
|
386
|
+
color: var(--text-dim);
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
font-size: 16px;
|
|
389
|
+
padding: 0 2px;
|
|
390
|
+
line-height: 1;
|
|
391
|
+
}
|
|
392
|
+
.btn-close-detail:hover { color: var(--text); }
|
|
393
|
+
.msg-detail-content {
|
|
394
|
+
flex: 1;
|
|
395
|
+
overflow-y: auto;
|
|
396
|
+
padding: 4px 0;
|
|
397
|
+
}
|
|
398
|
+
.msg-detail-meta {
|
|
399
|
+
font-size: 11px;
|
|
400
|
+
color: var(--text-dim);
|
|
401
|
+
padding: 4px 10px 6px;
|
|
402
|
+
border-bottom: 1px solid var(--border);
|
|
403
|
+
margin-bottom: 2px;
|
|
404
|
+
}
|
|
405
|
+
.msg-detail-meta strong { color: var(--text); }
|
|
406
|
+
.msg-detail-row {
|
|
407
|
+
display: grid;
|
|
408
|
+
grid-template-columns: 42px minmax(0,1fr) minmax(0,1.4fr);
|
|
409
|
+
gap: 0 6px;
|
|
410
|
+
padding: 2px 10px;
|
|
411
|
+
font-size: 11px;
|
|
412
|
+
line-height: 1.6;
|
|
413
|
+
}
|
|
414
|
+
.msg-detail-row:hover { background: var(--bg2); }
|
|
415
|
+
.detail-tag { color: var(--text-dim); font-variant-numeric: tabular-nums; }
|
|
416
|
+
.detail-name { color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
417
|
+
.detail-val { color: var(--text); word-break: break-all; }
|
|
418
|
+
|
|
345
419
|
/* ── Buttons ─────────────────────────────────────────────────────────── */
|
|
346
420
|
.btn {
|
|
347
421
|
font-family: var(--font);
|
|
@@ -71,6 +71,8 @@ class SocketInitiator:
|
|
|
71
71
|
async def stop(self) -> None:
|
|
72
72
|
"""Signal all connect loops to exit and wait for them to finish."""
|
|
73
73
|
self._stop_event.set()
|
|
74
|
+
for task in self._tasks:
|
|
75
|
+
task.cancel()
|
|
74
76
|
if self._tasks:
|
|
75
77
|
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
76
78
|
self._tasks.clear()
|
|
@@ -125,10 +127,9 @@ class SocketInitiator:
|
|
|
125
127
|
try:
|
|
126
128
|
while not self._stop_event.is_set():
|
|
127
129
|
try:
|
|
128
|
-
data = await
|
|
129
|
-
except asyncio.
|
|
130
|
-
|
|
131
|
-
except (ConnectionResetError, asyncio.IncompleteReadError):
|
|
130
|
+
data = await reader.read(4096)
|
|
131
|
+
except (ConnectionResetError, asyncio.IncompleteReadError,
|
|
132
|
+
asyncio.CancelledError):
|
|
132
133
|
break
|
|
133
134
|
if not data:
|
|
134
135
|
break
|
|
@@ -151,9 +152,6 @@ class SocketInitiator:
|
|
|
151
152
|
async def _interruptible_sleep(self, seconds: float) -> None:
|
|
152
153
|
"""Sleep for *seconds* but wake immediately if stop is requested."""
|
|
153
154
|
try:
|
|
154
|
-
await asyncio.wait_for(
|
|
155
|
-
asyncio.shield(self._stop_event.wait()),
|
|
156
|
-
timeout=seconds,
|
|
157
|
-
)
|
|
155
|
+
await asyncio.wait_for(self._stop_event.wait(), timeout=seconds)
|
|
158
156
|
except asyncio.TimeoutError:
|
|
159
157
|
pass
|
|
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
|