proofscan 0.10.32 → 0.10.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html/rpc-inspector.d.ts +15 -0
- package/dist/html/rpc-inspector.d.ts.map +1 -1
- package/dist/html/rpc-inspector.js +66 -0
- package/dist/html/rpc-inspector.js.map +1 -1
- package/dist/html/templates.d.ts +6 -0
- package/dist/html/templates.d.ts.map +1 -1
- package/dist/html/templates.js +566 -52
- package/dist/html/templates.js.map +1 -1
- package/dist/monitor/data/connectors.d.ts +19 -0
- package/dist/monitor/data/connectors.d.ts.map +1 -1
- package/dist/monitor/data/connectors.js +61 -0
- package/dist/monitor/data/connectors.js.map +1 -1
- package/dist/monitor/data/events.d.ts +21 -0
- package/dist/monitor/data/events.d.ts.map +1 -0
- package/dist/monitor/data/events.js +151 -0
- package/dist/monitor/data/events.js.map +1 -0
- package/dist/monitor/routes/api.d.ts.map +1 -1
- package/dist/monitor/routes/api.js +39 -1
- package/dist/monitor/routes/api.js.map +1 -1
- package/dist/monitor/routes/connectors.js +9 -7
- package/dist/monitor/routes/connectors.js.map +1 -1
- package/dist/monitor/templates/home.d.ts.map +1 -1
- package/dist/monitor/templates/home.js +1 -0
- package/dist/monitor/templates/home.js.map +1 -1
- package/dist/monitor/templates/layout.d.ts +1 -0
- package/dist/monitor/templates/layout.d.ts.map +1 -1
- package/dist/monitor/templates/layout.js +114 -44
- package/dist/monitor/templates/layout.js.map +1 -1
- package/dist/monitor/templates/popl.d.ts.map +1 -1
- package/dist/monitor/templates/popl.js +3 -0
- package/dist/monitor/templates/popl.js.map +1 -1
- package/dist/monitor/types.d.ts +24 -0
- package/dist/monitor/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/html/templates.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { formatBytes } from '../eventline/types.js';
|
|
8
8
|
import { getStatusSymbol, SHORT_ID_LENGTH } from './types.js';
|
|
9
|
-
import { getRpcInspectorStyles, getRpcInspectorScript, renderJsonWithPaths, renderRequestSummary, renderResponseSummary, renderSummaryRowsHtml, } from './rpc-inspector.js';
|
|
9
|
+
import { getRpcInspectorStyles, getRpcInspectorScript, renderJsonWithPaths, renderRequestSummary, renderResponseSummary, renderSummaryRowsHtml, detectSensitiveKeys, } from './rpc-inspector.js';
|
|
10
10
|
/**
|
|
11
11
|
* Escape HTML special characters to prevent XSS
|
|
12
12
|
*/
|
|
@@ -105,6 +105,21 @@ function getRpcReportStyles() {
|
|
|
105
105
|
.badge.status-OK { border-color: var(--status-ok); color: var(--status-ok); }
|
|
106
106
|
.badge.status-ERR { border-color: var(--status-err); color: var(--status-err); }
|
|
107
107
|
.badge.status-PENDING { border-color: var(--status-pending); color: var(--status-pending); }
|
|
108
|
+
/* Sensitive content warning badge (Phase 12.x-c) */
|
|
109
|
+
.sensitive-badge {
|
|
110
|
+
display: inline-flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 4px;
|
|
113
|
+
padding: 2px 8px;
|
|
114
|
+
margin-left: 8px;
|
|
115
|
+
background: rgba(210, 153, 34, 0.15);
|
|
116
|
+
border: 1px solid rgba(210, 153, 34, 0.3);
|
|
117
|
+
border-radius: 12px;
|
|
118
|
+
font-size: 11px;
|
|
119
|
+
font-weight: 500;
|
|
120
|
+
color: #d29922;
|
|
121
|
+
vertical-align: middle;
|
|
122
|
+
}
|
|
108
123
|
.section {
|
|
109
124
|
background: var(--bg-secondary);
|
|
110
125
|
border-radius: 8px;
|
|
@@ -231,6 +246,21 @@ function getSessionReportStyles() {
|
|
|
231
246
|
.badge.status-OK { border-color: var(--status-ok); color: var(--status-ok); }
|
|
232
247
|
.badge.status-ERR { border-color: var(--status-err); color: var(--status-err); }
|
|
233
248
|
.badge.status-PENDING { border-color: var(--status-pending); color: var(--status-pending); }
|
|
249
|
+
/* Sensitive content warning badge (Phase 12.x-c) */
|
|
250
|
+
.sensitive-badge {
|
|
251
|
+
display: inline-flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 4px;
|
|
254
|
+
padding: 2px 8px;
|
|
255
|
+
margin-left: 8px;
|
|
256
|
+
background: rgba(210, 153, 34, 0.15);
|
|
257
|
+
border: 1px solid rgba(210, 153, 34, 0.3);
|
|
258
|
+
border-radius: 12px;
|
|
259
|
+
font-size: 11px;
|
|
260
|
+
font-weight: 500;
|
|
261
|
+
color: #d29922;
|
|
262
|
+
vertical-align: middle;
|
|
263
|
+
}
|
|
234
264
|
/* Two-pane layout */
|
|
235
265
|
.container {
|
|
236
266
|
display: flex;
|
|
@@ -470,19 +500,29 @@ function getSessionReportScript() {
|
|
|
470
500
|
const requestRawHtml = rpc._requestRawHtml || '<span class="json-null">(no data)</span>';
|
|
471
501
|
const responseRawHtml = rpc._responseRawHtml || '<span class="json-null">(no data)</span>';
|
|
472
502
|
|
|
503
|
+
// Sensitive content warning badge (Phase 12.x-c)
|
|
504
|
+
// Escape keys to prevent XSS via malicious key names
|
|
505
|
+
const sensitiveKeys = (rpc._sensitiveKeys || []).map(function(k) { return escapeHtml(k); });
|
|
506
|
+
const sensitiveTooltip = sensitiveKeys.length > 5
|
|
507
|
+
? 'Contains ' + sensitiveKeys.length + ' sensitive keys: ' + sensitiveKeys.slice(0, 5).join(', ') + '...'
|
|
508
|
+
: 'Contains sensitive keys: ' + sensitiveKeys.join(', ');
|
|
509
|
+
const sensitiveBadge = rpc._hasSensitive
|
|
510
|
+
? '<span class="sensitive-badge" title="' + escapeHtml(sensitiveTooltip) + '">⚠ Sensitive</span>'
|
|
511
|
+
: '';
|
|
512
|
+
|
|
473
513
|
// Determine default target based on method (response-focused methods default to response)
|
|
474
514
|
const defaultTarget = (rpc.method === 'tools/list' || rpc.method === 'initialize' || rpc.method.startsWith('resources/') || rpc.method.startsWith('prompts/')) ? 'response' : 'request';
|
|
475
515
|
|
|
476
516
|
rightPane.innerHTML =
|
|
477
517
|
'<div class="detail-section">' +
|
|
478
|
-
' <h2>RPC Info</h2>' +
|
|
518
|
+
' <h2>RPC Info' + sensitiveBadge + '</h2>' +
|
|
479
519
|
' <div class="rpc-info-grid">' +
|
|
480
520
|
' <div class="rpc-info-item"><dt>RPC ID</dt><dd><span class="badge">' + escapeHtml(rpc.rpc_id) + '</span></dd></div>' +
|
|
481
521
|
' <div class="rpc-info-item"><dt>Method</dt><dd><span class="badge">' + escapeHtml(rpc.method) + '</span></dd></div>' +
|
|
482
522
|
' <div class="rpc-info-item"><dt>Status</dt><dd><span class="badge ' + statusClass + '">' + statusSymbol + ' ' + rpc.status + (rpc.error_code !== null ? ' (code: ' + rpc.error_code + ')' : '') + '</span></dd></div>' +
|
|
483
523
|
' <div class="rpc-info-item"><dt>Latency</dt><dd><span class="badge">' + latency + '</span></dd></div>' +
|
|
484
|
-
' <div class="rpc-info-item"><dt>
|
|
485
|
-
' <div class="rpc-info-item"><dt>
|
|
524
|
+
' <div class="rpc-info-item"><dt>Request</dt><dd>' + escapeHtml(rpc.request_ts) + '</dd></div>' +
|
|
525
|
+
' <div class="rpc-info-item"><dt>Response</dt><dd>' + escapeHtml(rpc.response_ts || '-') + '</dd></div>' +
|
|
486
526
|
' </div>' +
|
|
487
527
|
'</div>' +
|
|
488
528
|
'<div class="detail-section">' +
|
|
@@ -717,15 +757,22 @@ export function generateSessionHtml(report) {
|
|
|
717
757
|
const rpcRows = rpcs.map((rpc, idx) => renderRpcRow(rpc, idx)).join('\n');
|
|
718
758
|
// Pre-render summary and raw JSON HTML for each RPC (for RPC Inspector)
|
|
719
759
|
// Now generates separate request/response summaries for Req/Res toggle
|
|
760
|
+
// Also detect sensitive content for warning badge (Phase 12.x-c)
|
|
720
761
|
const rpcsWithInspectorHtml = rpcs.map((rpc) => {
|
|
721
762
|
const requestSummaryRows = renderRequestSummary(rpc.method, rpc.request.json);
|
|
722
763
|
const responseSummaryRows = renderResponseSummary(rpc.method, rpc.response.json);
|
|
764
|
+
// Detect sensitive keys in request/response
|
|
765
|
+
const reqSensitiveKeys = detectSensitiveKeys(rpc.request.json);
|
|
766
|
+
const resSensitiveKeys = detectSensitiveKeys(rpc.response.json);
|
|
767
|
+
const hasSensitive = reqSensitiveKeys.length > 0 || resSensitiveKeys.length > 0;
|
|
723
768
|
return {
|
|
724
769
|
...rpc,
|
|
725
770
|
_requestSummaryHtml: renderSummaryRowsHtml(requestSummaryRows),
|
|
726
771
|
_responseSummaryHtml: renderSummaryRowsHtml(responseSummaryRows),
|
|
727
772
|
_requestRawHtml: renderJsonWithPaths(rpc.request.json, '#'),
|
|
728
773
|
_responseRawHtml: renderJsonWithPaths(rpc.response.json, '#'),
|
|
774
|
+
_hasSensitive: hasSensitive,
|
|
775
|
+
_sensitiveKeys: [...reqSensitiveKeys, ...resSensitiveKeys],
|
|
729
776
|
};
|
|
730
777
|
});
|
|
731
778
|
const reportWithInspectorHtml = {
|
|
@@ -822,8 +869,9 @@ function getConnectorReportStyles() {
|
|
|
822
869
|
--status-pending: #d29922;
|
|
823
870
|
--border-color: #30363d;
|
|
824
871
|
--link-color: #58a6ff;
|
|
825
|
-
--sessions-pane-width:
|
|
826
|
-
--left-pane-width:
|
|
872
|
+
--sessions-pane-width: 360px;
|
|
873
|
+
--left-pane-width: 480px;
|
|
874
|
+
--raw-pane-max-width: 480px;
|
|
827
875
|
}
|
|
828
876
|
* { box-sizing: border-box; }
|
|
829
877
|
html, body {
|
|
@@ -972,6 +1020,21 @@ function getConnectorReportStyles() {
|
|
|
972
1020
|
.badge.status-PENDING { border-color: var(--status-pending); color: var(--status-pending); }
|
|
973
1021
|
.badge.cap-enabled { border-color: var(--accent-blue); color: var(--accent-blue); background: rgba(0, 212, 255, 0.1); }
|
|
974
1022
|
.badge.cap-disabled { border-color: var(--border-color); color: var(--text-secondary); background: transparent; opacity: 0.5; }
|
|
1023
|
+
/* Sensitive content warning badge (Phase 12.x-c) */
|
|
1024
|
+
.sensitive-badge {
|
|
1025
|
+
display: inline-flex;
|
|
1026
|
+
align-items: center;
|
|
1027
|
+
gap: 4px;
|
|
1028
|
+
padding: 2px 8px;
|
|
1029
|
+
margin-left: 8px;
|
|
1030
|
+
background: rgba(210, 153, 34, 0.15);
|
|
1031
|
+
border: 1px solid rgba(210, 153, 34, 0.3);
|
|
1032
|
+
border-radius: 12px;
|
|
1033
|
+
font-size: 11px;
|
|
1034
|
+
font-weight: 500;
|
|
1035
|
+
color: #d29922;
|
|
1036
|
+
vertical-align: middle;
|
|
1037
|
+
}
|
|
975
1038
|
|
|
976
1039
|
/* Connector info cards container (side by side) */
|
|
977
1040
|
.connector-info-cards {
|
|
@@ -1068,15 +1131,43 @@ function getConnectorReportStyles() {
|
|
|
1068
1131
|
.sessions-list {
|
|
1069
1132
|
flex: 1;
|
|
1070
1133
|
overflow-y: auto;
|
|
1071
|
-
padding: 8px;
|
|
1134
|
+
padding: 4px 8px;
|
|
1135
|
+
}
|
|
1136
|
+
.sessions-header-row {
|
|
1137
|
+
display: grid;
|
|
1138
|
+
grid-template-columns: 70px 1fr 60px 50px;
|
|
1139
|
+
gap: 8px;
|
|
1140
|
+
padding: 4px 8px;
|
|
1141
|
+
font-size: 10px;
|
|
1142
|
+
color: var(--text-secondary);
|
|
1143
|
+
text-transform: uppercase;
|
|
1144
|
+
border-bottom: 1px solid var(--border-color);
|
|
1145
|
+
margin-bottom: 4px;
|
|
1072
1146
|
}
|
|
1073
1147
|
.session-item {
|
|
1074
|
-
|
|
1075
|
-
|
|
1148
|
+
display: grid;
|
|
1149
|
+
grid-template-columns: 70px 1fr auto 50px 50px;
|
|
1150
|
+
gap: 8px;
|
|
1151
|
+
align-items: center;
|
|
1152
|
+
padding: 6px 8px;
|
|
1153
|
+
border-radius: 4px;
|
|
1076
1154
|
cursor: pointer;
|
|
1077
|
-
margin-bottom:
|
|
1155
|
+
margin-bottom: 2px;
|
|
1078
1156
|
border: 1px solid transparent;
|
|
1079
1157
|
background: var(--bg-primary);
|
|
1158
|
+
font-size: 11px;
|
|
1159
|
+
}
|
|
1160
|
+
.session-item .session-counts {
|
|
1161
|
+
display: flex;
|
|
1162
|
+
gap: 6px;
|
|
1163
|
+
font-size: 10px;
|
|
1164
|
+
color: var(--text-secondary);
|
|
1165
|
+
}
|
|
1166
|
+
.session-item .session-counts span {
|
|
1167
|
+
white-space: nowrap;
|
|
1168
|
+
}
|
|
1169
|
+
.session-item .session-extra {
|
|
1170
|
+
justify-self: end;
|
|
1080
1171
|
}
|
|
1081
1172
|
.session-item:hover {
|
|
1082
1173
|
background: rgba(0, 212, 255, 0.1);
|
|
@@ -1093,26 +1184,22 @@ function getConnectorReportStyles() {
|
|
|
1093
1184
|
0% { box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.6); }
|
|
1094
1185
|
100% { box-shadow: 0 0 0 0 rgba(0, 212, 255, 0); }
|
|
1095
1186
|
}
|
|
1096
|
-
.session-item-header {
|
|
1097
|
-
display: flex;
|
|
1098
|
-
align-items: center;
|
|
1099
|
-
justify-content: space-between;
|
|
1100
|
-
margin-bottom: 4px;
|
|
1101
|
-
}
|
|
1102
1187
|
.session-item .session-id {
|
|
1103
1188
|
font-family: 'SFMono-Regular', Consolas, monospace;
|
|
1104
1189
|
color: var(--accent-blue);
|
|
1105
|
-
|
|
1190
|
+
overflow: hidden;
|
|
1191
|
+
text-overflow: ellipsis;
|
|
1192
|
+
white-space: nowrap;
|
|
1106
1193
|
}
|
|
1107
|
-
.session-item .session-
|
|
1108
|
-
font-size: 0.75em;
|
|
1194
|
+
.session-item .session-timestamp {
|
|
1109
1195
|
color: var(--text-secondary);
|
|
1196
|
+
overflow: hidden;
|
|
1197
|
+
text-overflow: ellipsis;
|
|
1198
|
+
white-space: nowrap;
|
|
1110
1199
|
}
|
|
1111
|
-
.session-item .session-
|
|
1112
|
-
display: flex;
|
|
1113
|
-
gap: 8px;
|
|
1114
|
-
font-size: 0.75em;
|
|
1200
|
+
.session-item .session-latency {
|
|
1115
1201
|
color: var(--text-secondary);
|
|
1202
|
+
text-align: right;
|
|
1116
1203
|
}
|
|
1117
1204
|
|
|
1118
1205
|
/* Session detail pane (middle) */
|
|
@@ -1268,6 +1355,131 @@ function getConnectorReportStyles() {
|
|
|
1268
1355
|
background: var(--accent-blue);
|
|
1269
1356
|
}
|
|
1270
1357
|
|
|
1358
|
+
/* Events View Toggle (Issue #59) */
|
|
1359
|
+
.view-toggle {
|
|
1360
|
+
display: flex;
|
|
1361
|
+
gap: 2px;
|
|
1362
|
+
padding: 8px 12px;
|
|
1363
|
+
border-bottom: 1px solid var(--border-color);
|
|
1364
|
+
background: var(--bg-secondary);
|
|
1365
|
+
}
|
|
1366
|
+
.view-toggle-btn {
|
|
1367
|
+
background: transparent;
|
|
1368
|
+
border: 1px solid var(--border-color);
|
|
1369
|
+
color: var(--text-secondary);
|
|
1370
|
+
padding: 4px 12px;
|
|
1371
|
+
border-radius: 4px;
|
|
1372
|
+
cursor: pointer;
|
|
1373
|
+
font-size: 11px;
|
|
1374
|
+
transition: all 0.15s;
|
|
1375
|
+
}
|
|
1376
|
+
.view-toggle-btn:first-child {
|
|
1377
|
+
border-radius: 4px 0 0 4px;
|
|
1378
|
+
}
|
|
1379
|
+
.view-toggle-btn:last-child {
|
|
1380
|
+
border-radius: 0 4px 4px 0;
|
|
1381
|
+
}
|
|
1382
|
+
.view-toggle-btn:hover {
|
|
1383
|
+
border-color: var(--accent-blue);
|
|
1384
|
+
color: var(--text-primary);
|
|
1385
|
+
}
|
|
1386
|
+
.view-toggle-btn.active {
|
|
1387
|
+
background: rgba(0, 212, 255, 0.15);
|
|
1388
|
+
border-color: var(--accent-blue);
|
|
1389
|
+
color: var(--accent-blue);
|
|
1390
|
+
}
|
|
1391
|
+
.view-toggle-count {
|
|
1392
|
+
font-size: 10px;
|
|
1393
|
+
color: var(--text-secondary);
|
|
1394
|
+
margin-left: 4px;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/* Events List (Issue #59) */
|
|
1398
|
+
.events-list {
|
|
1399
|
+
flex: 1;
|
|
1400
|
+
overflow-y: auto;
|
|
1401
|
+
display: none;
|
|
1402
|
+
}
|
|
1403
|
+
.events-list.active {
|
|
1404
|
+
display: block;
|
|
1405
|
+
}
|
|
1406
|
+
.rpc-list.hidden {
|
|
1407
|
+
display: none;
|
|
1408
|
+
}
|
|
1409
|
+
.events-table {
|
|
1410
|
+
width: 100%;
|
|
1411
|
+
border-collapse: collapse;
|
|
1412
|
+
font-size: 0.85em;
|
|
1413
|
+
}
|
|
1414
|
+
.events-table th {
|
|
1415
|
+
text-align: left;
|
|
1416
|
+
color: var(--text-secondary);
|
|
1417
|
+
border-bottom: 1px solid var(--border-color);
|
|
1418
|
+
padding: 6px 8px;
|
|
1419
|
+
font-weight: 500;
|
|
1420
|
+
position: sticky;
|
|
1421
|
+
top: 0;
|
|
1422
|
+
background: var(--bg-primary);
|
|
1423
|
+
z-index: 1;
|
|
1424
|
+
}
|
|
1425
|
+
.events-table td {
|
|
1426
|
+
padding: 6px 8px;
|
|
1427
|
+
border-bottom: 1px solid var(--border-color);
|
|
1428
|
+
white-space: nowrap;
|
|
1429
|
+
}
|
|
1430
|
+
.event-row {
|
|
1431
|
+
cursor: pointer;
|
|
1432
|
+
}
|
|
1433
|
+
.event-row:hover {
|
|
1434
|
+
background: rgba(0, 212, 255, 0.1);
|
|
1435
|
+
}
|
|
1436
|
+
.event-row.selected {
|
|
1437
|
+
background: rgba(0, 212, 255, 0.2);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/* Event kind badges */
|
|
1441
|
+
.badge-kind-request {
|
|
1442
|
+
background: rgba(0, 212, 255, 0.15);
|
|
1443
|
+
color: var(--accent-blue);
|
|
1444
|
+
border: 1px solid rgba(0, 212, 255, 0.3);
|
|
1445
|
+
}
|
|
1446
|
+
.badge-kind-response {
|
|
1447
|
+
background: rgba(63, 185, 80, 0.15);
|
|
1448
|
+
color: var(--accent-green);
|
|
1449
|
+
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
1450
|
+
}
|
|
1451
|
+
.badge-kind-notification {
|
|
1452
|
+
background: rgba(210, 153, 34, 0.15);
|
|
1453
|
+
color: var(--accent-yellow);
|
|
1454
|
+
border: 1px solid rgba(210, 153, 34, 0.3);
|
|
1455
|
+
}
|
|
1456
|
+
.badge-kind-transport_event {
|
|
1457
|
+
background: var(--bg-tertiary);
|
|
1458
|
+
color: var(--text-secondary);
|
|
1459
|
+
border: 1px solid var(--border-color);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
/* Event direction arrows */
|
|
1463
|
+
.direction-arrow {
|
|
1464
|
+
font-size: 18px;
|
|
1465
|
+
font-weight: bold;
|
|
1466
|
+
line-height: 1;
|
|
1467
|
+
cursor: help;
|
|
1468
|
+
}
|
|
1469
|
+
.direction-arrow.outgoing {
|
|
1470
|
+
color: var(--accent-blue);
|
|
1471
|
+
}
|
|
1472
|
+
.direction-arrow.incoming {
|
|
1473
|
+
color: var(--accent-green);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/* Events loading state */
|
|
1477
|
+
.events-loading {
|
|
1478
|
+
padding: 24px;
|
|
1479
|
+
text-align: center;
|
|
1480
|
+
color: var(--text-secondary);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1271
1483
|
/* Analytics Panel (Phase 5.2) - Revised Layout */
|
|
1272
1484
|
|
|
1273
1485
|
/* Header with KPI stats inline */
|
|
@@ -1549,6 +1761,8 @@ function getConnectorReportScript() {
|
|
|
1549
1761
|
|
|
1550
1762
|
let currentSessionId = null;
|
|
1551
1763
|
let currentRpcIdx = null;
|
|
1764
|
+
let currentEventIdx = null;
|
|
1765
|
+
let currentViewMode = 'rpc'; // 'rpc' or 'events'
|
|
1552
1766
|
|
|
1553
1767
|
// Connector info toggle
|
|
1554
1768
|
const connectorInfo = document.querySelector('.connector-info');
|
|
@@ -1590,6 +1804,8 @@ function getConnectorReportScript() {
|
|
|
1590
1804
|
if (currentSessionId === sessionId) return;
|
|
1591
1805
|
currentSessionId = sessionId;
|
|
1592
1806
|
currentRpcIdx = null;
|
|
1807
|
+
currentEventIdx = null;
|
|
1808
|
+
currentViewMode = 'rpc'; // Reset to RPC view
|
|
1593
1809
|
|
|
1594
1810
|
// Update session list selection
|
|
1595
1811
|
document.querySelectorAll('.session-item').forEach(item => {
|
|
@@ -1641,19 +1857,29 @@ function getConnectorReportScript() {
|
|
|
1641
1857
|
const requestRawHtml = rpc._requestRawHtml || '<span class="json-null">(no data)</span>';
|
|
1642
1858
|
const responseRawHtml = rpc._responseRawHtml || '<span class="json-null">(no data)</span>';
|
|
1643
1859
|
|
|
1860
|
+
// Sensitive content warning badge (Phase 12.x-c)
|
|
1861
|
+
// Escape keys to prevent XSS via malicious key names
|
|
1862
|
+
const sensitiveKeys = (rpc._sensitiveKeys || []).map(function(k) { return escapeHtml(k); });
|
|
1863
|
+
const sensitiveTooltip = sensitiveKeys.length > 5
|
|
1864
|
+
? 'Contains ' + sensitiveKeys.length + ' sensitive keys: ' + sensitiveKeys.slice(0, 5).join(', ') + '...'
|
|
1865
|
+
: 'Contains sensitive keys: ' + sensitiveKeys.join(', ');
|
|
1866
|
+
const sensitiveBadge = rpc._hasSensitive
|
|
1867
|
+
? '<span class="sensitive-badge" title="' + escapeHtml(sensitiveTooltip) + '">⚠ Sensitive</span>'
|
|
1868
|
+
: '';
|
|
1869
|
+
|
|
1644
1870
|
// Determine default target based on method (response-focused methods default to response)
|
|
1645
1871
|
const defaultTarget = (rpc.method === 'tools/list' || rpc.method === 'initialize' || rpc.method.startsWith('resources/') || rpc.method.startsWith('prompts/')) ? 'response' : 'request';
|
|
1646
1872
|
|
|
1647
1873
|
rightPane.innerHTML =
|
|
1648
1874
|
'<div class="detail-section">' +
|
|
1649
|
-
' <h2>RPC Info</h2>' +
|
|
1875
|
+
' <h2>RPC Info' + sensitiveBadge + '</h2>' +
|
|
1650
1876
|
' <div class="rpc-info-grid">' +
|
|
1651
1877
|
' <div class="rpc-info-item"><dt>RPC ID</dt><dd><span class="badge">' + escapeHtml(rpc.rpc_id) + '</span></dd></div>' +
|
|
1652
1878
|
' <div class="rpc-info-item"><dt>Method</dt><dd><span class="badge">' + escapeHtml(rpc.method) + '</span></dd></div>' +
|
|
1653
1879
|
' <div class="rpc-info-item"><dt>Status</dt><dd><span class="badge ' + statusClass + '">' + statusSymbol + ' ' + rpc.status + (rpc.error_code !== null ? ' (code: ' + rpc.error_code + ')' : '') + '</span></dd></div>' +
|
|
1654
1880
|
' <div class="rpc-info-item"><dt>Latency</dt><dd><span class="badge">' + latency + '</span></dd></div>' +
|
|
1655
|
-
' <div class="rpc-info-item"><dt>
|
|
1656
|
-
' <div class="rpc-info-item"><dt>
|
|
1881
|
+
' <div class="rpc-info-item"><dt>Request</dt><dd>' + escapeHtml(rpc.request_ts) + '</dd></div>' +
|
|
1882
|
+
' <div class="rpc-info-item"><dt>Response</dt><dd>' + escapeHtml(rpc.response_ts || '-') + '</dd></div>' +
|
|
1657
1883
|
' </div>' +
|
|
1658
1884
|
'</div>' +
|
|
1659
1885
|
'<div class="detail-section">' +
|
|
@@ -1715,16 +1941,46 @@ function getConnectorReportScript() {
|
|
|
1715
1941
|
});
|
|
1716
1942
|
});
|
|
1717
1943
|
|
|
1718
|
-
// Keyboard navigation
|
|
1944
|
+
// Keyboard navigation (handles both RPC and Events views)
|
|
1719
1945
|
document.addEventListener('keydown', (e) => {
|
|
1720
1946
|
if (!currentSessionId) return;
|
|
1947
|
+
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;
|
|
1721
1948
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1949
|
+
e.preventDefault();
|
|
1950
|
+
const sessionContent = document.querySelector('.session-content[data-session-id="' + currentSessionId + '"]');
|
|
1951
|
+
if (!sessionContent) return;
|
|
1724
1952
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1953
|
+
// Check which view is active
|
|
1954
|
+
if (currentViewMode === 'events') {
|
|
1955
|
+
// Events navigation
|
|
1956
|
+
const eventRows = sessionContent.querySelectorAll('.event-row');
|
|
1957
|
+
if (eventRows.length === 0) return;
|
|
1958
|
+
|
|
1959
|
+
let newIdx = currentEventIdx;
|
|
1960
|
+
if (currentEventIdx === null) {
|
|
1961
|
+
newIdx = 0;
|
|
1962
|
+
} else if (e.key === 'ArrowDown' && currentEventIdx < eventRows.length - 1) {
|
|
1963
|
+
newIdx = currentEventIdx + 1;
|
|
1964
|
+
} else if (e.key === 'ArrowUp' && currentEventIdx > 0) {
|
|
1965
|
+
newIdx = currentEventIdx - 1;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
if (newIdx !== currentEventIdx) {
|
|
1969
|
+
currentEventIdx = newIdx;
|
|
1970
|
+
// Update selection visually
|
|
1971
|
+
eventRows.forEach((r, i) => r.classList.toggle('selected', i === newIdx));
|
|
1972
|
+
// Scroll into view
|
|
1973
|
+
eventRows[newIdx].scrollIntoView({ block: 'nearest' });
|
|
1974
|
+
// Trigger click to show detail (if has payload)
|
|
1975
|
+
eventRows[newIdx].click();
|
|
1976
|
+
}
|
|
1977
|
+
} else {
|
|
1978
|
+
// RPC navigation
|
|
1979
|
+
const report = sessionReports[currentSessionId];
|
|
1980
|
+
if (!report) return;
|
|
1727
1981
|
const rpcs = report.rpcs;
|
|
1982
|
+
if (rpcs.length === 0) return;
|
|
1983
|
+
|
|
1728
1984
|
if (currentRpcIdx === null && rpcs.length > 0) {
|
|
1729
1985
|
showRpcDetail(currentSessionId, 0);
|
|
1730
1986
|
return;
|
|
@@ -1735,11 +1991,8 @@ function getConnectorReportScript() {
|
|
|
1735
1991
|
showRpcDetail(currentSessionId, currentRpcIdx - 1);
|
|
1736
1992
|
}
|
|
1737
1993
|
// Scroll selected row into view
|
|
1738
|
-
const
|
|
1739
|
-
if (
|
|
1740
|
-
const row = sessionContent.querySelector('.rpc-row.selected');
|
|
1741
|
-
if (row) row.scrollIntoView({ block: 'nearest' });
|
|
1742
|
-
}
|
|
1994
|
+
const row = sessionContent.querySelector('.rpc-row.selected');
|
|
1995
|
+
if (row) row.scrollIntoView({ block: 'nearest' });
|
|
1743
1996
|
}
|
|
1744
1997
|
});
|
|
1745
1998
|
|
|
@@ -1806,6 +2059,225 @@ function getConnectorReportScript() {
|
|
|
1806
2059
|
showSession(sessions[0].session_id);
|
|
1807
2060
|
}
|
|
1808
2061
|
|
|
2062
|
+
// Events View toggle and data loading (Issue #59)
|
|
2063
|
+
(function() {
|
|
2064
|
+
// Cache for loaded events
|
|
2065
|
+
const eventsCache = {};
|
|
2066
|
+
|
|
2067
|
+
// Event kind display labels
|
|
2068
|
+
const kindLabels = {
|
|
2069
|
+
request: 'REQ',
|
|
2070
|
+
response: 'RES',
|
|
2071
|
+
notification: 'NOTIF',
|
|
2072
|
+
transport_event: 'TRANS'
|
|
2073
|
+
};
|
|
2074
|
+
|
|
2075
|
+
// Format time for events table
|
|
2076
|
+
function formatEventTime(ts) {
|
|
2077
|
+
try {
|
|
2078
|
+
const date = new Date(ts);
|
|
2079
|
+
return date.toISOString().split('T')[1].slice(0, 12);
|
|
2080
|
+
} catch {
|
|
2081
|
+
return ts;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// Render events table
|
|
2086
|
+
function renderEventsTable(events) {
|
|
2087
|
+
if (!events || events.length === 0) {
|
|
2088
|
+
return '<div class="events-loading">No events in this session</div>';
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
const rows = events.map(function(event, idx) {
|
|
2092
|
+
const dirClass = event.direction === 'client_to_server' ? 'outgoing' : 'incoming';
|
|
2093
|
+
// Large arrows with tooltip: ⇨ (blue) = Client→Server, ⇦ (green) = Server→Client
|
|
2094
|
+
const dirArrow = event.direction === 'client_to_server' ? '\\u21E8' : '\\u21E6';
|
|
2095
|
+
const dirTooltip = event.direction === 'client_to_server'
|
|
2096
|
+
? 'Client \\u2192 Server'
|
|
2097
|
+
: 'Server \\u2192 Client';
|
|
2098
|
+
const kindClass = 'badge-kind-' + event.kind;
|
|
2099
|
+
const kindLabel = kindLabels[event.kind] || event.kind;
|
|
2100
|
+
// Method/Summary fallback: method > summary > payload_type (e.g., "connected")
|
|
2101
|
+
const method = event.method || event.summary || event.payload_type || '';
|
|
2102
|
+
const timeStr = formatEventTime(event.ts);
|
|
2103
|
+
const hasPayload = event.has_payload ? '\\u2713' : '';
|
|
2104
|
+
|
|
2105
|
+
return '<tr class="event-row" data-event-idx="' + idx + '" data-event-id="' + escapeHtml(event.event_id) + '">' +
|
|
2106
|
+
'<td>' + timeStr + '</td>' +
|
|
2107
|
+
'<td><span class="direction-arrow ' + dirClass + '" title="' + dirTooltip + '">' + dirArrow + '</span></td>' +
|
|
2108
|
+
'<td><span class="badge ' + kindClass + '">' + kindLabel + '</span></td>' +
|
|
2109
|
+
'<td>' + escapeHtml(method) + '</td>' +
|
|
2110
|
+
'<td>' + hasPayload + '</td>' +
|
|
2111
|
+
'</tr>';
|
|
2112
|
+
}).join('');
|
|
2113
|
+
|
|
2114
|
+
return '<table class="events-table">' +
|
|
2115
|
+
'<thead><tr>' +
|
|
2116
|
+
'<th>Time</th>' +
|
|
2117
|
+
'<th>Dir</th>' +
|
|
2118
|
+
'<th>Kind</th>' +
|
|
2119
|
+
'<th>Method/Summary</th>' +
|
|
2120
|
+
'<th>Data</th>' +
|
|
2121
|
+
'</tr></thead>' +
|
|
2122
|
+
'<tbody>' + rows + '</tbody>' +
|
|
2123
|
+
'</table>';
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// Load events for a session
|
|
2127
|
+
function loadEvents(sessionId, eventsList) {
|
|
2128
|
+
if (eventsCache[sessionId]) {
|
|
2129
|
+
eventsList.innerHTML = renderEventsTable(eventsCache[sessionId]);
|
|
2130
|
+
// Attach click handlers even when using cached data
|
|
2131
|
+
attachEventRowHandlers(eventsList, sessionId, eventsCache[sessionId]);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// Check if we're in offline mode (static HTML) or live server
|
|
2136
|
+
// Try to fetch from API, fallback to "no data" message
|
|
2137
|
+
eventsList.innerHTML = '<div class="events-loading">Loading events...</div>';
|
|
2138
|
+
|
|
2139
|
+
fetch('/api/sessions/' + encodeURIComponent(sessionId) + '/events')
|
|
2140
|
+
.then(function(res) {
|
|
2141
|
+
if (!res.ok) throw new Error('API not available');
|
|
2142
|
+
return res.json();
|
|
2143
|
+
})
|
|
2144
|
+
.then(function(data) {
|
|
2145
|
+
eventsCache[sessionId] = data.events;
|
|
2146
|
+
eventsList.innerHTML = renderEventsTable(data.events);
|
|
2147
|
+
// Attach click handlers for event rows
|
|
2148
|
+
attachEventRowHandlers(eventsList, sessionId, data.events);
|
|
2149
|
+
})
|
|
2150
|
+
.catch(function() {
|
|
2151
|
+
eventsList.innerHTML = '<div class="events-loading">Events data not available (API offline)</div>';
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// Attach click handlers for event rows
|
|
2156
|
+
function attachEventRowHandlers(eventsList, sessionId, events) {
|
|
2157
|
+
eventsList.querySelectorAll('.event-row').forEach(function(row) {
|
|
2158
|
+
row.addEventListener('click', function() {
|
|
2159
|
+
const idx = parseInt(row.dataset.eventIdx);
|
|
2160
|
+
const event = events[idx];
|
|
2161
|
+
if (!event || !event.has_payload) return;
|
|
2162
|
+
|
|
2163
|
+
// Clear previous selection
|
|
2164
|
+
eventsList.querySelectorAll('.event-row').forEach(function(r) {
|
|
2165
|
+
r.classList.remove('selected');
|
|
2166
|
+
});
|
|
2167
|
+
row.classList.add('selected');
|
|
2168
|
+
|
|
2169
|
+
// Show event detail in right pane
|
|
2170
|
+
showEventDetail(sessionId, event);
|
|
2171
|
+
});
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// Show event detail in right pane (2-column layout like RPC Inspector)
|
|
2176
|
+
function showEventDetail(sessionId, event) {
|
|
2177
|
+
const sessionContent = document.querySelector('.session-content[data-session-id="' + sessionId + '"]');
|
|
2178
|
+
if (!sessionContent) return;
|
|
2179
|
+
|
|
2180
|
+
const rightPane = sessionContent.querySelector('.right-pane');
|
|
2181
|
+
if (!rightPane) return;
|
|
2182
|
+
|
|
2183
|
+
// Fetch full event detail
|
|
2184
|
+
fetch('/api/events/' + encodeURIComponent(event.event_id))
|
|
2185
|
+
.then(function(res) {
|
|
2186
|
+
if (!res.ok) throw new Error('Event not found');
|
|
2187
|
+
return res.json();
|
|
2188
|
+
})
|
|
2189
|
+
.then(function(data) {
|
|
2190
|
+
const evt = data.event;
|
|
2191
|
+
const kindClass = 'badge-kind-' + evt.kind;
|
|
2192
|
+
const dirClass = evt.direction === 'client_to_server' ? 'outgoing' : 'incoming';
|
|
2193
|
+
// Large arrows with tooltip: ⇨ (blue) = Client→Server, ⇦ (green) = Server→Client
|
|
2194
|
+
const dirArrow = evt.direction === 'client_to_server' ? '\\u21E8' : '\\u21E6';
|
|
2195
|
+
const dirTooltip = evt.direction === 'client_to_server'
|
|
2196
|
+
? 'Client \\u2192 Server'
|
|
2197
|
+
: 'Server \\u2192 Client';
|
|
2198
|
+
const method = evt.method || evt.summary || '(unknown)';
|
|
2199
|
+
const rawJson = evt.raw_json ? JSON.parse(evt.raw_json) : null;
|
|
2200
|
+
const formattedJson = rawJson ? JSON.stringify(rawJson, null, 2) : '(no data)';
|
|
2201
|
+
|
|
2202
|
+
// Build summary section
|
|
2203
|
+
var summaryHtml = '<div class="summary-row summary-header">Event Info</div>';
|
|
2204
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">Kind</span><span class="summary-prop-value"><span class="badge ' + kindClass + '">' + evt.kind + '</span></span></div>';
|
|
2205
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">Direction</span><span class="summary-prop-value"><span class="direction-arrow ' + dirClass + '" title="' + dirTooltip + '">' + dirArrow + '</span> ' + dirTooltip + '</span></div>';
|
|
2206
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">Method</span><span class="summary-prop-value">' + escapeHtml(method) + '</span></div>';
|
|
2207
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">Timestamp</span><span class="summary-prop-value">' + escapeHtml(evt.ts) + '</span></div>';
|
|
2208
|
+
if (evt.seq !== null) {
|
|
2209
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">Sequence</span><span class="summary-prop-value">' + evt.seq + '</span></div>';
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// If JSON has recognizable structure, add more summary
|
|
2213
|
+
if (rawJson) {
|
|
2214
|
+
if (rawJson.method) {
|
|
2215
|
+
summaryHtml += '<div class="summary-row summary-header" style="margin-top: 12px;">JSON-RPC</div>';
|
|
2216
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">method</span><span class="summary-prop-value">' + escapeHtml(rawJson.method) + '</span></div>';
|
|
2217
|
+
}
|
|
2218
|
+
if (rawJson.id !== undefined) {
|
|
2219
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">id</span><span class="summary-prop-value">' + escapeHtml(String(rawJson.id)) + '</span></div>';
|
|
2220
|
+
}
|
|
2221
|
+
if (rawJson.error) {
|
|
2222
|
+
summaryHtml += '<div class="summary-row summary-property"><span class="summary-prop-name">error</span><span class="summary-prop-value" style="color: var(--accent-red);">' + escapeHtml(JSON.stringify(rawJson.error)) + '</span></div>';
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
// 2-column layout: Summary (left) + Raw JSON (right)
|
|
2227
|
+
rightPane.innerHTML =
|
|
2228
|
+
'<div class="rpc-inspector">' +
|
|
2229
|
+
'<div class="rpc-inspector-summary" style="flex: 0 0 280px; max-width: 320px;">' +
|
|
2230
|
+
'<div class="summary-container">' + summaryHtml + '</div>' +
|
|
2231
|
+
'</div>' +
|
|
2232
|
+
'<div class="rpc-inspector-raw" style="flex: 1; min-width: 0;">' +
|
|
2233
|
+
'<div class="rpc-raw-header">' +
|
|
2234
|
+
'<span class="rpc-raw-title">Payload</span>' +
|
|
2235
|
+
'</div>' +
|
|
2236
|
+
'<div class="rpc-raw-json"><pre><code>' + escapeHtml(formattedJson) + '</code></pre></div>' +
|
|
2237
|
+
'</div>' +
|
|
2238
|
+
'</div>';
|
|
2239
|
+
})
|
|
2240
|
+
.catch(function() {
|
|
2241
|
+
rightPane.innerHTML = '<div class="detail-placeholder">Failed to load event detail</div>';
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// Handle view toggle clicks
|
|
2246
|
+
document.querySelectorAll('.view-toggle').forEach(function(toggle) {
|
|
2247
|
+
const sessionContent = toggle.closest('.session-content');
|
|
2248
|
+
if (!sessionContent) return;
|
|
2249
|
+
|
|
2250
|
+
const sessionId = sessionContent.dataset.sessionId;
|
|
2251
|
+
const rpcList = sessionContent.querySelector('.rpc-list');
|
|
2252
|
+
const eventsList = sessionContent.querySelector('.events-list');
|
|
2253
|
+
const buttons = toggle.querySelectorAll('.view-toggle-btn');
|
|
2254
|
+
|
|
2255
|
+
buttons.forEach(function(btn) {
|
|
2256
|
+
btn.addEventListener('click', function() {
|
|
2257
|
+
const view = btn.dataset.view;
|
|
2258
|
+
|
|
2259
|
+
// Update current view mode
|
|
2260
|
+
currentViewMode = view;
|
|
2261
|
+
|
|
2262
|
+
// Update button states
|
|
2263
|
+
buttons.forEach(function(b) {
|
|
2264
|
+
b.classList.toggle('active', b.dataset.view === view);
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
// Toggle lists
|
|
2268
|
+
if (view === 'events') {
|
|
2269
|
+
rpcList.classList.add('hidden');
|
|
2270
|
+
eventsList.classList.add('active');
|
|
2271
|
+
loadEvents(sessionId, eventsList);
|
|
2272
|
+
} else {
|
|
2273
|
+
rpcList.classList.remove('hidden');
|
|
2274
|
+
eventsList.classList.remove('active');
|
|
2275
|
+
}
|
|
2276
|
+
});
|
|
2277
|
+
});
|
|
2278
|
+
});
|
|
2279
|
+
})();
|
|
2280
|
+
|
|
1809
2281
|
// RPC Inspector script
|
|
1810
2282
|
${getRpcInspectorScript()}
|
|
1811
2283
|
`;
|
|
@@ -2187,27 +2659,46 @@ function renderAnalyticsPanel(analytics) {
|
|
|
2187
2659
|
</div>`;
|
|
2188
2660
|
}
|
|
2189
2661
|
/**
|
|
2190
|
-
* Render a session item for the sessions pane
|
|
2662
|
+
* Render a session item for the sessions pane (compact grid view)
|
|
2191
2663
|
*/
|
|
2192
2664
|
function renderConnectorSessionItem(session) {
|
|
2193
|
-
|
|
2194
|
-
const
|
|
2195
|
-
const
|
|
2196
|
-
?
|
|
2197
|
-
: '
|
|
2665
|
+
// Format timestamp compactly: MM/DD HH:MM
|
|
2666
|
+
const timestamp = formatCompactTimestamp(session.started_at);
|
|
2667
|
+
const latencyStr = session.total_latency_ms !== null
|
|
2668
|
+
? `${session.total_latency_ms}ms`
|
|
2669
|
+
: '-';
|
|
2198
2670
|
return `
|
|
2199
|
-
<div class="session-item"
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
</
|
|
2204
|
-
<
|
|
2205
|
-
<
|
|
2206
|
-
|
|
2207
|
-
${session.error_count > 0 ? `<span style="color: var(--status-err)">${session.error_count} errors</span>` : ''}
|
|
2208
|
-
</div>
|
|
2671
|
+
<div class="session-item"
|
|
2672
|
+
data-session-id="${escapeHtml(session.session_id)}"
|
|
2673
|
+
title="Session: ${session.session_id} Started: ${session.started_at} RPCs: ${session.rpc_count} Events: ${session.event_count} Errors: ${session.error_count}">
|
|
2674
|
+
<span class="session-id">[${escapeHtml(session.short_id)}]</span>
|
|
2675
|
+
<span class="session-timestamp">${timestamp}</span>
|
|
2676
|
+
<span class="session-counts"><span>R:${session.rpc_count}</span><span>E:${session.event_count}</span></span>
|
|
2677
|
+
<span class="session-latency">${latencyStr}</span>
|
|
2678
|
+
<span class="session-extra"></span>
|
|
2209
2679
|
</div>`;
|
|
2210
2680
|
}
|
|
2681
|
+
/**
|
|
2682
|
+
* Format timestamp compactly for grid display (UTC)
|
|
2683
|
+
* Returns format: HH:MM:SS.mmm (time with milliseconds)
|
|
2684
|
+
* @public - exported for testing
|
|
2685
|
+
*/
|
|
2686
|
+
export function formatCompactTimestamp(isoStr) {
|
|
2687
|
+
try {
|
|
2688
|
+
const date = new Date(isoStr);
|
|
2689
|
+
if (isNaN(date.getTime())) {
|
|
2690
|
+
return '-';
|
|
2691
|
+
}
|
|
2692
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
2693
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
2694
|
+
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
|
|
2695
|
+
const millis = String(date.getUTCMilliseconds()).padStart(3, '0');
|
|
2696
|
+
return `${hours}:${minutes}:${seconds}.${millis}`;
|
|
2697
|
+
}
|
|
2698
|
+
catch {
|
|
2699
|
+
return '-';
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2211
2702
|
/**
|
|
2212
2703
|
* Render session detail content (reuses session HTML layout)
|
|
2213
2704
|
*/
|
|
@@ -2254,6 +2745,14 @@ function renderSessionDetailContent(sessionId, report) {
|
|
|
2254
2745
|
<dd><span class="badge">${totalLatencyDisplay}</span></dd>
|
|
2255
2746
|
</dl>
|
|
2256
2747
|
</div>
|
|
2748
|
+
<div class="view-toggle">
|
|
2749
|
+
<button class="view-toggle-btn active" data-view="rpc">
|
|
2750
|
+
RPCs<span class="view-toggle-count">(${session.rpc_count})</span>
|
|
2751
|
+
</button>
|
|
2752
|
+
<button class="view-toggle-btn" data-view="events">
|
|
2753
|
+
Events<span class="view-toggle-count">(${session.event_count})</span>
|
|
2754
|
+
</button>
|
|
2755
|
+
</div>
|
|
2257
2756
|
<div class="rpc-list">
|
|
2258
2757
|
<table class="rpc-table">
|
|
2259
2758
|
<thead>
|
|
@@ -2270,6 +2769,9 @@ ${rpcRows}
|
|
|
2270
2769
|
</tbody>
|
|
2271
2770
|
</table>
|
|
2272
2771
|
</div>
|
|
2772
|
+
<div class="events-list">
|
|
2773
|
+
<div class="events-loading">Loading events...</div>
|
|
2774
|
+
</div>
|
|
2273
2775
|
</div>
|
|
2274
2776
|
<div class="resize-handle"></div>
|
|
2275
2777
|
<div class="right-pane">
|
|
@@ -2345,12 +2847,18 @@ export function generateConnectorHtml(report) {
|
|
|
2345
2847
|
rpcs: sessionReport.rpcs.map((rpc) => {
|
|
2346
2848
|
const requestSummaryRows = renderRequestSummary(rpc.method, rpc.request.json);
|
|
2347
2849
|
const responseSummaryRows = renderResponseSummary(rpc.method, rpc.response.json);
|
|
2850
|
+
// Detect sensitive keys in request/response (Phase 12.x-c)
|
|
2851
|
+
const reqSensitiveKeys = detectSensitiveKeys(rpc.request.json);
|
|
2852
|
+
const resSensitiveKeys = detectSensitiveKeys(rpc.response.json);
|
|
2853
|
+
const hasSensitive = reqSensitiveKeys.length > 0 || resSensitiveKeys.length > 0;
|
|
2348
2854
|
return {
|
|
2349
2855
|
...rpc,
|
|
2350
2856
|
_requestSummaryHtml: renderSummaryRowsHtml(requestSummaryRows),
|
|
2351
2857
|
_responseSummaryHtml: renderSummaryRowsHtml(responseSummaryRows),
|
|
2352
2858
|
_requestRawHtml: renderJsonWithPaths(rpc.request.json, '#'),
|
|
2353
2859
|
_responseRawHtml: renderJsonWithPaths(rpc.response.json, '#'),
|
|
2860
|
+
_hasSensitive: hasSensitive,
|
|
2861
|
+
_sensitiveKeys: [...reqSensitiveKeys, ...resSensitiveKeys],
|
|
2354
2862
|
};
|
|
2355
2863
|
}),
|
|
2356
2864
|
};
|
|
@@ -2422,6 +2930,12 @@ export function generateConnectorHtml(report) {
|
|
|
2422
2930
|
<h2>Sessions</h2>
|
|
2423
2931
|
<span class="pagination-info">${paginationInfo}</span>
|
|
2424
2932
|
</div>
|
|
2933
|
+
<div class="sessions-header-row">
|
|
2934
|
+
<span>ID</span>
|
|
2935
|
+
<span>Time (UTC)</span>
|
|
2936
|
+
<span style="text-align:right">Latency</span>
|
|
2937
|
+
<span></span>
|
|
2938
|
+
</div>
|
|
2425
2939
|
<div class="sessions-list">
|
|
2426
2940
|
${sessionItems}
|
|
2427
2941
|
</div>
|