proofscan 0.10.25 → 0.10.26
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/commands/connectors.d.ts.map +1 -1
- package/dist/commands/connectors.js +322 -1
- package/dist/commands/connectors.js.map +1 -1
- package/dist/html/index.d.ts +3 -3
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +2 -2
- package/dist/html/index.js.map +1 -1
- package/dist/html/templates.d.ts +5 -1
- package/dist/html/templates.d.ts.map +1 -1
- package/dist/html/templates.js +785 -1
- package/dist/html/templates.js.map +1 -1
- package/dist/html/types.d.ts +72 -0
- package/dist/html/types.d.ts.map +1 -1
- package/dist/html/types.js +12 -0
- package/dist/html/types.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +3 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +3 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +3 -0
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/shell/router-commands.d.ts.map +1 -1
- package/dist/shell/router-commands.js +13 -6
- package/dist/shell/router-commands.js.map +1 -1
- package/dist/shell/types.js +3 -3
- package/dist/shell/types.js.map +1 -1
- package/package.json +1 -1
package/dist/html/templates.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Dark theme with neon blue accent badges.
|
|
6
6
|
*/
|
|
7
7
|
import { formatBytes } from '../eventline/types.js';
|
|
8
|
-
import { getStatusSymbol } from './types.js';
|
|
8
|
+
import { getStatusSymbol, SHORT_ID_LENGTH } from './types.js';
|
|
9
9
|
/**
|
|
10
10
|
* Escape HTML special characters to prevent XSS
|
|
11
11
|
*/
|
|
@@ -753,4 +753,788 @@ ${rpcRows}
|
|
|
753
753
|
</body>
|
|
754
754
|
</html>`;
|
|
755
755
|
}
|
|
756
|
+
// ============================================================================
|
|
757
|
+
// Connector HTML Report (Phase 5.1)
|
|
758
|
+
// ============================================================================
|
|
759
|
+
/**
|
|
760
|
+
* Get CSS styles for Connector HTML (3-hierarchy: Connector -> Sessions -> RPCs)
|
|
761
|
+
*/
|
|
762
|
+
function getConnectorReportStyles() {
|
|
763
|
+
return `
|
|
764
|
+
:root {
|
|
765
|
+
--bg-primary: #0d1117;
|
|
766
|
+
--bg-secondary: #161b22;
|
|
767
|
+
--bg-tertiary: #21262d;
|
|
768
|
+
--text-primary: #e6edf3;
|
|
769
|
+
--text-secondary: #8b949e;
|
|
770
|
+
--accent-blue: #00d4ff;
|
|
771
|
+
--status-ok: #3fb950;
|
|
772
|
+
--status-err: #f85149;
|
|
773
|
+
--status-pending: #d29922;
|
|
774
|
+
--border-color: #30363d;
|
|
775
|
+
--link-color: #58a6ff;
|
|
776
|
+
--sessions-pane-width: 280px;
|
|
777
|
+
--left-pane-width: 420px;
|
|
778
|
+
}
|
|
779
|
+
* { box-sizing: border-box; }
|
|
780
|
+
html, body {
|
|
781
|
+
height: 100%;
|
|
782
|
+
margin: 0;
|
|
783
|
+
padding: 0;
|
|
784
|
+
overflow: hidden;
|
|
785
|
+
}
|
|
786
|
+
body {
|
|
787
|
+
background: var(--bg-primary);
|
|
788
|
+
color: var(--text-primary);
|
|
789
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
790
|
+
line-height: 1.5;
|
|
791
|
+
display: flex;
|
|
792
|
+
flex-direction: column;
|
|
793
|
+
}
|
|
794
|
+
header {
|
|
795
|
+
padding: 12px 20px;
|
|
796
|
+
border-bottom: 1px solid var(--border-color);
|
|
797
|
+
flex-shrink: 0;
|
|
798
|
+
}
|
|
799
|
+
h1 {
|
|
800
|
+
margin: 0 0 4px 0;
|
|
801
|
+
font-size: 1.3em;
|
|
802
|
+
font-weight: 600;
|
|
803
|
+
}
|
|
804
|
+
h2 {
|
|
805
|
+
margin: 0 0 8px 0;
|
|
806
|
+
font-size: 1em;
|
|
807
|
+
font-weight: 600;
|
|
808
|
+
color: var(--text-primary);
|
|
809
|
+
border-bottom: 1px solid var(--border-color);
|
|
810
|
+
padding-bottom: 6px;
|
|
811
|
+
}
|
|
812
|
+
h3 {
|
|
813
|
+
margin: 12px 0 6px 0;
|
|
814
|
+
font-size: 0.9em;
|
|
815
|
+
font-weight: 600;
|
|
816
|
+
color: var(--text-secondary);
|
|
817
|
+
}
|
|
818
|
+
h3:first-child { margin-top: 0; }
|
|
819
|
+
a { color: var(--link-color); text-decoration: none; }
|
|
820
|
+
a:hover { text-decoration: underline; }
|
|
821
|
+
.meta { color: var(--text-secondary); margin: 0; font-size: 0.8em; }
|
|
822
|
+
.badge {
|
|
823
|
+
display: inline-block;
|
|
824
|
+
padding: 1px 6px;
|
|
825
|
+
border: 1px solid var(--accent-blue);
|
|
826
|
+
border-radius: 4px;
|
|
827
|
+
color: var(--accent-blue);
|
|
828
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
829
|
+
font-size: 0.8em;
|
|
830
|
+
background: transparent;
|
|
831
|
+
}
|
|
832
|
+
.badge.status-OK { border-color: var(--status-ok); color: var(--status-ok); }
|
|
833
|
+
.badge.status-ERR { border-color: var(--status-err); color: var(--status-err); }
|
|
834
|
+
.badge.status-PENDING { border-color: var(--status-pending); color: var(--status-pending); }
|
|
835
|
+
.badge.cap-enabled { border-color: var(--accent-blue); color: var(--accent-blue); background: rgba(0, 212, 255, 0.1); }
|
|
836
|
+
|
|
837
|
+
/* Connector info section (collapsible) */
|
|
838
|
+
.connector-info {
|
|
839
|
+
background: var(--bg-secondary);
|
|
840
|
+
padding: 12px 20px;
|
|
841
|
+
border-bottom: 1px solid var(--border-color);
|
|
842
|
+
flex-shrink: 0;
|
|
843
|
+
}
|
|
844
|
+
.connector-info-toggle {
|
|
845
|
+
display: flex;
|
|
846
|
+
align-items: center;
|
|
847
|
+
justify-content: space-between;
|
|
848
|
+
cursor: pointer;
|
|
849
|
+
user-select: none;
|
|
850
|
+
}
|
|
851
|
+
.connector-info-toggle h2 {
|
|
852
|
+
margin: 0;
|
|
853
|
+
border: none;
|
|
854
|
+
padding: 0;
|
|
855
|
+
}
|
|
856
|
+
.connector-info-toggle .toggle-icon {
|
|
857
|
+
color: var(--text-secondary);
|
|
858
|
+
font-size: 0.85em;
|
|
859
|
+
transition: transform 0.2s;
|
|
860
|
+
}
|
|
861
|
+
.connector-info-content {
|
|
862
|
+
display: none;
|
|
863
|
+
margin-top: 12px;
|
|
864
|
+
}
|
|
865
|
+
.connector-info.expanded .connector-info-content {
|
|
866
|
+
display: block;
|
|
867
|
+
}
|
|
868
|
+
.connector-info.expanded .toggle-icon {
|
|
869
|
+
transform: rotate(180deg);
|
|
870
|
+
}
|
|
871
|
+
.connector-info dl {
|
|
872
|
+
display: grid;
|
|
873
|
+
grid-template-columns: auto 1fr;
|
|
874
|
+
gap: 4px 16px;
|
|
875
|
+
margin: 0;
|
|
876
|
+
font-size: 0.85em;
|
|
877
|
+
}
|
|
878
|
+
.connector-info dt { color: var(--text-secondary); }
|
|
879
|
+
.connector-info dd { margin: 0; }
|
|
880
|
+
.capabilities {
|
|
881
|
+
display: flex;
|
|
882
|
+
gap: 6px;
|
|
883
|
+
flex-wrap: wrap;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/* Main 3-pane container */
|
|
887
|
+
.main-container {
|
|
888
|
+
display: flex;
|
|
889
|
+
flex: 1;
|
|
890
|
+
overflow: hidden;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/* Sessions pane (leftmost) */
|
|
894
|
+
.sessions-pane {
|
|
895
|
+
width: var(--sessions-pane-width);
|
|
896
|
+
flex-shrink: 0;
|
|
897
|
+
border-right: 1px solid var(--border-color);
|
|
898
|
+
display: flex;
|
|
899
|
+
flex-direction: column;
|
|
900
|
+
overflow: hidden;
|
|
901
|
+
background: var(--bg-secondary);
|
|
902
|
+
}
|
|
903
|
+
.sessions-header {
|
|
904
|
+
padding: 10px 12px;
|
|
905
|
+
border-bottom: 1px solid var(--border-color);
|
|
906
|
+
background: var(--bg-tertiary);
|
|
907
|
+
flex-shrink: 0;
|
|
908
|
+
}
|
|
909
|
+
.sessions-header h2 {
|
|
910
|
+
margin: 0;
|
|
911
|
+
border: none;
|
|
912
|
+
padding: 0;
|
|
913
|
+
font-size: 0.9em;
|
|
914
|
+
}
|
|
915
|
+
.sessions-list {
|
|
916
|
+
flex: 1;
|
|
917
|
+
overflow-y: auto;
|
|
918
|
+
padding: 8px;
|
|
919
|
+
}
|
|
920
|
+
.session-item {
|
|
921
|
+
padding: 10px 12px;
|
|
922
|
+
border-radius: 6px;
|
|
923
|
+
cursor: pointer;
|
|
924
|
+
margin-bottom: 6px;
|
|
925
|
+
border: 1px solid transparent;
|
|
926
|
+
background: var(--bg-primary);
|
|
927
|
+
}
|
|
928
|
+
.session-item:hover {
|
|
929
|
+
background: rgba(0, 212, 255, 0.1);
|
|
930
|
+
border-color: rgba(0, 212, 255, 0.3);
|
|
931
|
+
}
|
|
932
|
+
.session-item.selected {
|
|
933
|
+
border-color: var(--accent-blue);
|
|
934
|
+
background: rgba(0, 212, 255, 0.15);
|
|
935
|
+
}
|
|
936
|
+
.session-item-header {
|
|
937
|
+
display: flex;
|
|
938
|
+
align-items: center;
|
|
939
|
+
justify-content: space-between;
|
|
940
|
+
margin-bottom: 4px;
|
|
941
|
+
}
|
|
942
|
+
.session-item .session-id {
|
|
943
|
+
font-family: 'SFMono-Regular', Consolas, monospace;
|
|
944
|
+
color: var(--accent-blue);
|
|
945
|
+
font-size: 0.85em;
|
|
946
|
+
}
|
|
947
|
+
.session-item .session-meta {
|
|
948
|
+
font-size: 0.75em;
|
|
949
|
+
color: var(--text-secondary);
|
|
950
|
+
}
|
|
951
|
+
.session-item .session-stats {
|
|
952
|
+
display: flex;
|
|
953
|
+
gap: 8px;
|
|
954
|
+
font-size: 0.75em;
|
|
955
|
+
color: var(--text-secondary);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/* Session detail pane (middle) */
|
|
959
|
+
.session-detail-pane {
|
|
960
|
+
flex: 1;
|
|
961
|
+
display: flex;
|
|
962
|
+
flex-direction: column;
|
|
963
|
+
overflow: hidden;
|
|
964
|
+
min-width: 0;
|
|
965
|
+
}
|
|
966
|
+
.session-detail-empty {
|
|
967
|
+
flex: 1;
|
|
968
|
+
display: flex;
|
|
969
|
+
align-items: center;
|
|
970
|
+
justify-content: center;
|
|
971
|
+
color: var(--text-secondary);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/* Re-use session HTML styles for the detail view */
|
|
975
|
+
.session-content {
|
|
976
|
+
display: none;
|
|
977
|
+
flex-direction: column;
|
|
978
|
+
height: 100%;
|
|
979
|
+
overflow: hidden;
|
|
980
|
+
}
|
|
981
|
+
.session-content.active {
|
|
982
|
+
display: flex;
|
|
983
|
+
}
|
|
984
|
+
.inner-container {
|
|
985
|
+
display: flex;
|
|
986
|
+
flex: 1;
|
|
987
|
+
overflow: hidden;
|
|
988
|
+
}
|
|
989
|
+
.left-pane {
|
|
990
|
+
width: var(--left-pane-width);
|
|
991
|
+
min-width: 300px;
|
|
992
|
+
max-width: 600px;
|
|
993
|
+
border-right: 1px solid var(--border-color);
|
|
994
|
+
display: flex;
|
|
995
|
+
flex-direction: column;
|
|
996
|
+
overflow: hidden;
|
|
997
|
+
}
|
|
998
|
+
.right-pane {
|
|
999
|
+
flex: 1;
|
|
1000
|
+
overflow-y: auto;
|
|
1001
|
+
padding: 16px;
|
|
1002
|
+
}
|
|
1003
|
+
.session-info {
|
|
1004
|
+
background: var(--bg-secondary);
|
|
1005
|
+
padding: 12px;
|
|
1006
|
+
border-bottom: 1px solid var(--border-color);
|
|
1007
|
+
flex-shrink: 0;
|
|
1008
|
+
}
|
|
1009
|
+
.session-info dl {
|
|
1010
|
+
display: grid;
|
|
1011
|
+
grid-template-columns: auto 1fr;
|
|
1012
|
+
gap: 4px 12px;
|
|
1013
|
+
margin: 0;
|
|
1014
|
+
font-size: 0.85em;
|
|
1015
|
+
}
|
|
1016
|
+
.session-info dt { color: var(--text-secondary); }
|
|
1017
|
+
.session-info dd { margin: 0; }
|
|
1018
|
+
.rpc-list {
|
|
1019
|
+
flex: 1;
|
|
1020
|
+
overflow-y: auto;
|
|
1021
|
+
}
|
|
1022
|
+
.rpc-table {
|
|
1023
|
+
width: 100%;
|
|
1024
|
+
border-collapse: collapse;
|
|
1025
|
+
font-size: 0.85em;
|
|
1026
|
+
}
|
|
1027
|
+
.rpc-table th {
|
|
1028
|
+
text-align: left;
|
|
1029
|
+
color: var(--text-secondary);
|
|
1030
|
+
border-bottom: 1px solid var(--border-color);
|
|
1031
|
+
padding: 6px 8px;
|
|
1032
|
+
font-weight: 500;
|
|
1033
|
+
position: sticky;
|
|
1034
|
+
top: 0;
|
|
1035
|
+
background: var(--bg-primary);
|
|
1036
|
+
z-index: 1;
|
|
1037
|
+
}
|
|
1038
|
+
.rpc-table td {
|
|
1039
|
+
padding: 6px 8px;
|
|
1040
|
+
border-bottom: 1px solid var(--border-color);
|
|
1041
|
+
white-space: nowrap;
|
|
1042
|
+
}
|
|
1043
|
+
.rpc-row {
|
|
1044
|
+
cursor: pointer;
|
|
1045
|
+
}
|
|
1046
|
+
.rpc-row:hover {
|
|
1047
|
+
background: rgba(0, 212, 255, 0.1);
|
|
1048
|
+
}
|
|
1049
|
+
.rpc-row.selected {
|
|
1050
|
+
background: rgba(0, 212, 255, 0.2);
|
|
1051
|
+
}
|
|
1052
|
+
.detail-placeholder {
|
|
1053
|
+
color: var(--text-secondary);
|
|
1054
|
+
text-align: center;
|
|
1055
|
+
padding: 40px;
|
|
1056
|
+
}
|
|
1057
|
+
.detail-section {
|
|
1058
|
+
background: var(--bg-secondary);
|
|
1059
|
+
border-radius: 8px;
|
|
1060
|
+
padding: 16px;
|
|
1061
|
+
margin-bottom: 16px;
|
|
1062
|
+
}
|
|
1063
|
+
pre {
|
|
1064
|
+
background: var(--bg-primary);
|
|
1065
|
+
border: 1px solid var(--border-color);
|
|
1066
|
+
border-radius: 6px;
|
|
1067
|
+
padding: 12px;
|
|
1068
|
+
overflow-x: auto;
|
|
1069
|
+
margin: 8px 0;
|
|
1070
|
+
font-size: 0.85em;
|
|
1071
|
+
max-height: 400px;
|
|
1072
|
+
overflow-y: auto;
|
|
1073
|
+
}
|
|
1074
|
+
code {
|
|
1075
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
1076
|
+
color: var(--text-primary);
|
|
1077
|
+
}
|
|
1078
|
+
.copy-btn {
|
|
1079
|
+
background: var(--bg-secondary);
|
|
1080
|
+
border: 1px solid var(--border-color);
|
|
1081
|
+
color: var(--text-secondary);
|
|
1082
|
+
padding: 3px 6px;
|
|
1083
|
+
border-radius: 4px;
|
|
1084
|
+
cursor: pointer;
|
|
1085
|
+
font-size: 0.75em;
|
|
1086
|
+
margin-left: 8px;
|
|
1087
|
+
}
|
|
1088
|
+
.copy-btn:hover {
|
|
1089
|
+
border-color: var(--accent-blue);
|
|
1090
|
+
color: var(--accent-blue);
|
|
1091
|
+
}
|
|
1092
|
+
.truncated-note {
|
|
1093
|
+
color: var(--status-pending);
|
|
1094
|
+
font-size: 0.8em;
|
|
1095
|
+
margin: 4px 0;
|
|
1096
|
+
}
|
|
1097
|
+
.spill-link {
|
|
1098
|
+
color: var(--link-color);
|
|
1099
|
+
font-size: 0.8em;
|
|
1100
|
+
}
|
|
1101
|
+
.resize-handle {
|
|
1102
|
+
width: 4px;
|
|
1103
|
+
background: var(--border-color);
|
|
1104
|
+
cursor: col-resize;
|
|
1105
|
+
transition: background 0.2s;
|
|
1106
|
+
}
|
|
1107
|
+
.resize-handle:hover {
|
|
1108
|
+
background: var(--accent-blue);
|
|
1109
|
+
}
|
|
1110
|
+
`;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Get JavaScript for Connector HTML (3-hierarchy navigation)
|
|
1114
|
+
*/
|
|
1115
|
+
function getConnectorReportScript() {
|
|
1116
|
+
return `
|
|
1117
|
+
// Report data
|
|
1118
|
+
const reportData = JSON.parse(document.getElementById('report-data').textContent);
|
|
1119
|
+
const sessions = reportData.sessions;
|
|
1120
|
+
const sessionReports = reportData.session_reports;
|
|
1121
|
+
|
|
1122
|
+
let currentSessionId = null;
|
|
1123
|
+
let currentRpcIdx = null;
|
|
1124
|
+
|
|
1125
|
+
// Connector info toggle
|
|
1126
|
+
const connectorInfo = document.querySelector('.connector-info');
|
|
1127
|
+
const connectorToggle = document.querySelector('.connector-info-toggle');
|
|
1128
|
+
if (connectorToggle) {
|
|
1129
|
+
connectorToggle.addEventListener('click', () => {
|
|
1130
|
+
connectorInfo.classList.toggle('expanded');
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Format JSON for display
|
|
1135
|
+
function formatJson(data) {
|
|
1136
|
+
if (data === null || data === undefined) return '(no data)';
|
|
1137
|
+
try {
|
|
1138
|
+
return JSON.stringify(data, null, 2);
|
|
1139
|
+
} catch {
|
|
1140
|
+
return String(data);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Escape HTML
|
|
1145
|
+
function escapeHtml(text) {
|
|
1146
|
+
const div = document.createElement('div');
|
|
1147
|
+
div.textContent = text;
|
|
1148
|
+
return div.innerHTML;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Format bytes
|
|
1152
|
+
function formatBytes(bytes) {
|
|
1153
|
+
if (bytes === 0) return '0 B';
|
|
1154
|
+
const k = 1024;
|
|
1155
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
1156
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1157
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Show session detail
|
|
1161
|
+
function showSession(sessionId) {
|
|
1162
|
+
if (currentSessionId === sessionId) return;
|
|
1163
|
+
currentSessionId = sessionId;
|
|
1164
|
+
currentRpcIdx = null;
|
|
1165
|
+
|
|
1166
|
+
// Update session list selection
|
|
1167
|
+
document.querySelectorAll('.session-item').forEach(item => {
|
|
1168
|
+
item.classList.toggle('selected', item.dataset.sessionId === sessionId);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
// Hide all session contents, show selected
|
|
1172
|
+
document.querySelectorAll('.session-content').forEach(content => {
|
|
1173
|
+
content.classList.toggle('active', content.dataset.sessionId === sessionId);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// Select first RPC in the newly shown session
|
|
1177
|
+
const sessionContent = document.querySelector('.session-content[data-session-id="' + sessionId + '"]');
|
|
1178
|
+
if (sessionContent) {
|
|
1179
|
+
const firstRpcRow = sessionContent.querySelector('.rpc-row');
|
|
1180
|
+
if (firstRpcRow) {
|
|
1181
|
+
const idx = parseInt(firstRpcRow.dataset.rpcIdx);
|
|
1182
|
+
showRpcDetail(sessionId, idx);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Show RPC detail in right pane
|
|
1188
|
+
function showRpcDetail(sessionId, idx) {
|
|
1189
|
+
const report = sessionReports[sessionId];
|
|
1190
|
+
if (!report || idx < 0 || idx >= report.rpcs.length) return;
|
|
1191
|
+
|
|
1192
|
+
const rpc = report.rpcs[idx];
|
|
1193
|
+
const sessionContent = document.querySelector('.session-content[data-session-id="' + sessionId + '"]');
|
|
1194
|
+
if (!sessionContent) return;
|
|
1195
|
+
|
|
1196
|
+
const rightPane = sessionContent.querySelector('.right-pane');
|
|
1197
|
+
if (!rightPane) return;
|
|
1198
|
+
|
|
1199
|
+
// Update RPC row selection
|
|
1200
|
+
sessionContent.querySelectorAll('.rpc-row').forEach((r, i) => {
|
|
1201
|
+
r.classList.toggle('selected', i === idx);
|
|
1202
|
+
});
|
|
1203
|
+
currentRpcIdx = idx;
|
|
1204
|
+
|
|
1205
|
+
const statusClass = 'status-' + rpc.status;
|
|
1206
|
+
const statusSymbol = rpc.status === 'OK' ? '\\u2713' : rpc.status === 'ERR' ? '\\u2717' : '?';
|
|
1207
|
+
const latency = rpc.latency_ms !== null ? rpc.latency_ms + 'ms' : '(pending)';
|
|
1208
|
+
|
|
1209
|
+
function renderPayload(payload, elementId) {
|
|
1210
|
+
let content, notes = '';
|
|
1211
|
+
|
|
1212
|
+
if (payload.truncated) {
|
|
1213
|
+
notes = '<p class="truncated-note">Payload truncated (' + formatBytes(payload.size) + ', showing first 4096 chars)</p>';
|
|
1214
|
+
if (payload.spillFile) {
|
|
1215
|
+
notes += '<p class="spill-link">Full payload: <a href="' + escapeHtml(payload.spillFile) + '">' + escapeHtml(payload.spillFile) + '</a></p>';
|
|
1216
|
+
}
|
|
1217
|
+
content = payload.preview ? escapeHtml(payload.preview) + '\\n... (truncated)' : '(no data)';
|
|
1218
|
+
} else if (payload.json !== null) {
|
|
1219
|
+
content = escapeHtml(formatJson(payload.json));
|
|
1220
|
+
} else {
|
|
1221
|
+
content = '(no data)';
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return notes + '<pre id="' + elementId + '"><code>' + content + '</code></pre>';
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
rightPane.innerHTML =
|
|
1228
|
+
'<div class="detail-section">' +
|
|
1229
|
+
' <h2>RPC Info</h2>' +
|
|
1230
|
+
' <dl class="session-info">' +
|
|
1231
|
+
' <dt>RPC ID</dt><dd><span class="badge">' + escapeHtml(rpc.rpc_id) + '</span></dd>' +
|
|
1232
|
+
' <dt>Method</dt><dd><span class="badge">' + escapeHtml(rpc.method) + '</span></dd>' +
|
|
1233
|
+
' <dt>Status</dt><dd><span class="badge ' + statusClass + '">' + statusSymbol + ' ' + rpc.status + (rpc.error_code !== null ? ' (code: ' + rpc.error_code + ')' : '') + '</span></dd>' +
|
|
1234
|
+
' <dt>Latency</dt><dd><span class="badge">' + latency + '</span></dd>' +
|
|
1235
|
+
' <dt>Request Size</dt><dd>' + formatBytes(rpc.request.size) + '</dd>' +
|
|
1236
|
+
' <dt>Response Size</dt><dd>' + formatBytes(rpc.response.size) + '</dd>' +
|
|
1237
|
+
' </dl>' +
|
|
1238
|
+
'</div>' +
|
|
1239
|
+
'<div class="detail-section">' +
|
|
1240
|
+
' <h2>Request <button class="copy-btn" onclick="copyToClipboard(\\'req-' + sessionId + '-' + idx + '\\', this)">Copy</button></h2>' +
|
|
1241
|
+
' ' + renderPayload(rpc.request, 'req-' + sessionId + '-' + idx) +
|
|
1242
|
+
'</div>' +
|
|
1243
|
+
'<div class="detail-section">' +
|
|
1244
|
+
' <h2>Response <button class="copy-btn" onclick="copyToClipboard(\\'res-' + sessionId + '-' + idx + '\\', this)">Copy</button></h2>' +
|
|
1245
|
+
' ' + renderPayload(rpc.response, 'res-' + sessionId + '-' + idx) +
|
|
1246
|
+
'</div>';
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Copy to clipboard
|
|
1250
|
+
async function copyToClipboard(elementId, btn) {
|
|
1251
|
+
const target = document.getElementById(elementId);
|
|
1252
|
+
if (target) {
|
|
1253
|
+
try {
|
|
1254
|
+
await navigator.clipboard.writeText(target.textContent || '');
|
|
1255
|
+
const originalText = btn.textContent;
|
|
1256
|
+
btn.textContent = 'Copied!';
|
|
1257
|
+
setTimeout(() => { btn.textContent = originalText; }, 1500);
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
console.error('Copy failed:', err);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// Session item click handlers
|
|
1265
|
+
document.querySelectorAll('.session-item').forEach(item => {
|
|
1266
|
+
item.addEventListener('click', () => {
|
|
1267
|
+
showSession(item.dataset.sessionId);
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
// RPC row click handlers (delegated)
|
|
1272
|
+
document.querySelectorAll('.session-content').forEach(content => {
|
|
1273
|
+
content.addEventListener('click', (e) => {
|
|
1274
|
+
const row = e.target.closest('.rpc-row');
|
|
1275
|
+
if (row) {
|
|
1276
|
+
const idx = parseInt(row.dataset.rpcIdx);
|
|
1277
|
+
showRpcDetail(content.dataset.sessionId, idx);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
// Keyboard navigation
|
|
1283
|
+
document.addEventListener('keydown', (e) => {
|
|
1284
|
+
if (!currentSessionId) return;
|
|
1285
|
+
|
|
1286
|
+
const report = sessionReports[currentSessionId];
|
|
1287
|
+
if (!report) return;
|
|
1288
|
+
|
|
1289
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
1290
|
+
e.preventDefault();
|
|
1291
|
+
const rpcs = report.rpcs;
|
|
1292
|
+
if (currentRpcIdx === null && rpcs.length > 0) {
|
|
1293
|
+
showRpcDetail(currentSessionId, 0);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
if (e.key === 'ArrowDown' && currentRpcIdx < rpcs.length - 1) {
|
|
1297
|
+
showRpcDetail(currentSessionId, currentRpcIdx + 1);
|
|
1298
|
+
} else if (e.key === 'ArrowUp' && currentRpcIdx > 0) {
|
|
1299
|
+
showRpcDetail(currentSessionId, currentRpcIdx - 1);
|
|
1300
|
+
}
|
|
1301
|
+
// Scroll selected row into view
|
|
1302
|
+
const sessionContent = document.querySelector('.session-content[data-session-id="' + currentSessionId + '"]');
|
|
1303
|
+
if (sessionContent) {
|
|
1304
|
+
const row = sessionContent.querySelector('.rpc-row.selected');
|
|
1305
|
+
if (row) row.scrollIntoView({ block: 'nearest' });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// Resize handle for inner left pane
|
|
1311
|
+
document.querySelectorAll('.session-content').forEach(content => {
|
|
1312
|
+
const resizeHandle = content.querySelector('.resize-handle');
|
|
1313
|
+
const leftPane = content.querySelector('.left-pane');
|
|
1314
|
+
if (resizeHandle && leftPane) {
|
|
1315
|
+
let startX, startWidth;
|
|
1316
|
+
|
|
1317
|
+
resizeHandle.addEventListener('mousedown', (e) => {
|
|
1318
|
+
startX = e.clientX;
|
|
1319
|
+
startWidth = leftPane.offsetWidth;
|
|
1320
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
1321
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
1322
|
+
e.preventDefault();
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
function onMouseMove(e) {
|
|
1326
|
+
const diff = e.clientX - startX;
|
|
1327
|
+
const newWidth = Math.max(300, Math.min(600, startWidth + diff));
|
|
1328
|
+
leftPane.style.width = newWidth + 'px';
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function onMouseUp() {
|
|
1332
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
1333
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
// Select first session by default
|
|
1339
|
+
if (sessions.length > 0) {
|
|
1340
|
+
showSession(sessions[0].session_id);
|
|
1341
|
+
}
|
|
1342
|
+
`;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Render a session item for the sessions pane
|
|
1346
|
+
*/
|
|
1347
|
+
function renderConnectorSessionItem(session) {
|
|
1348
|
+
const timeStr = formatTimestamp(session.started_at).split(' ')[1]?.slice(0, 8) || '-';
|
|
1349
|
+
const dateStr = formatTimestamp(session.started_at).split(' ')[0] || '-';
|
|
1350
|
+
const statusBadge = session.error_count > 0
|
|
1351
|
+
? '<span class="badge status-ERR">ERR</span>'
|
|
1352
|
+
: '<span class="badge status-OK">OK</span>';
|
|
1353
|
+
return `
|
|
1354
|
+
<div class="session-item" data-session-id="${escapeHtml(session.session_id)}">
|
|
1355
|
+
<div class="session-item-header">
|
|
1356
|
+
<span class="session-id">[${escapeHtml(session.short_id)}]</span>
|
|
1357
|
+
${statusBadge}
|
|
1358
|
+
</div>
|
|
1359
|
+
<div class="session-meta">${dateStr} ${timeStr}</div>
|
|
1360
|
+
<div class="session-stats">
|
|
1361
|
+
<span>${session.rpc_count} RPCs</span>
|
|
1362
|
+
${session.error_count > 0 ? `<span style="color: var(--status-err)">${session.error_count} errors</span>` : ''}
|
|
1363
|
+
</div>
|
|
1364
|
+
</div>`;
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Render session detail content (reuses session HTML layout)
|
|
1368
|
+
*/
|
|
1369
|
+
function renderSessionDetailContent(sessionId, report) {
|
|
1370
|
+
const { session, rpcs } = report;
|
|
1371
|
+
const totalLatencyDisplay = session.total_latency_ms !== null
|
|
1372
|
+
? `${session.total_latency_ms}ms`
|
|
1373
|
+
: '-';
|
|
1374
|
+
const rpcRows = rpcs.map((rpc, idx) => {
|
|
1375
|
+
const statusClass = `status-${rpc.status}`;
|
|
1376
|
+
const statusSymbol = getStatusSymbol(rpc.status);
|
|
1377
|
+
const rpcIdShort = rpc.rpc_id.slice(0, SHORT_ID_LENGTH);
|
|
1378
|
+
const timeShort = formatTimestamp(rpc.request_ts).split(' ')[1]?.slice(0, 12) || '-';
|
|
1379
|
+
const latency = rpc.latency_ms !== null ? `${rpc.latency_ms}ms` : '-';
|
|
1380
|
+
return `
|
|
1381
|
+
<tr class="rpc-row" data-rpc-idx="${idx}">
|
|
1382
|
+
<td>${timeShort}</td>
|
|
1383
|
+
<td><span class="badge ${statusClass}">${statusSymbol}</span></td>
|
|
1384
|
+
<td><span class="badge">${escapeHtml(rpcIdShort)}</span></td>
|
|
1385
|
+
<td>${escapeHtml(rpc.method)}</td>
|
|
1386
|
+
<td>${latency}</td>
|
|
1387
|
+
</tr>`;
|
|
1388
|
+
}).join('\n');
|
|
1389
|
+
return `
|
|
1390
|
+
<div class="session-content" data-session-id="${escapeHtml(sessionId)}">
|
|
1391
|
+
<div class="inner-container">
|
|
1392
|
+
<div class="left-pane">
|
|
1393
|
+
<div class="session-info">
|
|
1394
|
+
<h2>Session Info</h2>
|
|
1395
|
+
<dl>
|
|
1396
|
+
<dt>Session ID</dt>
|
|
1397
|
+
<dd><span class="badge">${escapeHtml(session.session_id)}</span></dd>
|
|
1398
|
+
<dt>Started</dt>
|
|
1399
|
+
<dd>${formatTimestamp(session.started_at)}</dd>
|
|
1400
|
+
<dt>Ended</dt>
|
|
1401
|
+
<dd>${session.ended_at ? formatTimestamp(session.ended_at) : '(active)'}</dd>
|
|
1402
|
+
<dt>Exit Reason</dt>
|
|
1403
|
+
<dd>${session.exit_reason || '(none)'}</dd>
|
|
1404
|
+
<dt>RPC Count</dt>
|
|
1405
|
+
<dd><span class="badge">${session.rpc_count}</span></dd>
|
|
1406
|
+
<dt>Event Count</dt>
|
|
1407
|
+
<dd><span class="badge">${session.event_count}</span></dd>
|
|
1408
|
+
<dt>Total Latency</dt>
|
|
1409
|
+
<dd><span class="badge">${totalLatencyDisplay}</span></dd>
|
|
1410
|
+
</dl>
|
|
1411
|
+
</div>
|
|
1412
|
+
<div class="rpc-list">
|
|
1413
|
+
<table class="rpc-table">
|
|
1414
|
+
<thead>
|
|
1415
|
+
<tr>
|
|
1416
|
+
<th>Time</th>
|
|
1417
|
+
<th>St</th>
|
|
1418
|
+
<th>ID</th>
|
|
1419
|
+
<th>Method</th>
|
|
1420
|
+
<th>Latency</th>
|
|
1421
|
+
</tr>
|
|
1422
|
+
</thead>
|
|
1423
|
+
<tbody>
|
|
1424
|
+
${rpcRows}
|
|
1425
|
+
</tbody>
|
|
1426
|
+
</table>
|
|
1427
|
+
</div>
|
|
1428
|
+
</div>
|
|
1429
|
+
<div class="resize-handle"></div>
|
|
1430
|
+
<div class="right-pane">
|
|
1431
|
+
<div class="detail-placeholder">
|
|
1432
|
+
${rpcs.length > 0 ? 'Select an RPC call from the list to view details' : 'No RPC calls in this session'}
|
|
1433
|
+
</div>
|
|
1434
|
+
</div>
|
|
1435
|
+
</div>
|
|
1436
|
+
</div>`;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Generate Connector HTML report (3-hierarchy: Connector -> Sessions -> RPCs)
|
|
1440
|
+
*/
|
|
1441
|
+
export function generateConnectorHtml(report) {
|
|
1442
|
+
const { meta, connector, sessions, session_reports } = report;
|
|
1443
|
+
// Pagination info
|
|
1444
|
+
const fromNum = connector.offset + 1;
|
|
1445
|
+
const toNum = connector.offset + connector.displayed_sessions;
|
|
1446
|
+
const paginationInfo = connector.session_count > 0
|
|
1447
|
+
? `Showing ${fromNum}-${toNum} of ${connector.session_count} sessions`
|
|
1448
|
+
: 'No sessions';
|
|
1449
|
+
// Connector info section
|
|
1450
|
+
const transportDisplay = connector.transport.type === 'stdio'
|
|
1451
|
+
? connector.transport.command || '(unknown command)'
|
|
1452
|
+
: connector.transport.url || '(unknown URL)';
|
|
1453
|
+
// Server info (if available)
|
|
1454
|
+
let serverInfoHtml = '';
|
|
1455
|
+
if (connector.server) {
|
|
1456
|
+
const { name, version, protocolVersion, capabilities } = connector.server;
|
|
1457
|
+
const serverName = name || '(unknown)';
|
|
1458
|
+
const serverVersion = version ? `v${version}` : '';
|
|
1459
|
+
const protocolDisplay = protocolVersion ? `MCP ${protocolVersion}` : '';
|
|
1460
|
+
// Capabilities badges
|
|
1461
|
+
const capBadges = [];
|
|
1462
|
+
if (capabilities.tools)
|
|
1463
|
+
capBadges.push('<span class="badge cap-enabled">tools</span>');
|
|
1464
|
+
if (capabilities.resources)
|
|
1465
|
+
capBadges.push('<span class="badge cap-enabled">resources</span>');
|
|
1466
|
+
if (capabilities.prompts)
|
|
1467
|
+
capBadges.push('<span class="badge cap-enabled">prompts</span>');
|
|
1468
|
+
const capsDisplay = capBadges.length > 0 ? capBadges.join(' ') : '<span style="color: var(--text-secondary)">(none)</span>';
|
|
1469
|
+
serverInfoHtml = `
|
|
1470
|
+
<dt>Server</dt>
|
|
1471
|
+
<dd>${escapeHtml(serverName)} ${escapeHtml(serverVersion)}</dd>
|
|
1472
|
+
<dt>Protocol</dt>
|
|
1473
|
+
<dd>${escapeHtml(protocolDisplay)}</dd>
|
|
1474
|
+
<dt>Capabilities</dt>
|
|
1475
|
+
<dd class="capabilities">${capsDisplay}</dd>`;
|
|
1476
|
+
}
|
|
1477
|
+
// Session items
|
|
1478
|
+
const sessionItems = sessions.map(s => renderConnectorSessionItem(s)).join('\n');
|
|
1479
|
+
// Session contents (pre-rendered, hidden by default)
|
|
1480
|
+
const sessionContents = sessions.map(s => {
|
|
1481
|
+
const sessionReport = session_reports[s.session_id];
|
|
1482
|
+
if (!sessionReport)
|
|
1483
|
+
return '';
|
|
1484
|
+
return renderSessionDetailContent(s.session_id, sessionReport);
|
|
1485
|
+
}).join('\n');
|
|
1486
|
+
const embeddedJson = escapeJsonForScript(JSON.stringify(report));
|
|
1487
|
+
return `<!DOCTYPE html>
|
|
1488
|
+
<html lang="en">
|
|
1489
|
+
<head>
|
|
1490
|
+
<meta charset="UTF-8">
|
|
1491
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1492
|
+
<title>Connector: ${escapeHtml(connector.connector_id)} - proofscan</title>
|
|
1493
|
+
<style>${getConnectorReportStyles()}</style>
|
|
1494
|
+
</head>
|
|
1495
|
+
<body>
|
|
1496
|
+
<header>
|
|
1497
|
+
<h1>Connector: <span class="badge">${escapeHtml(connector.connector_id)}</span></h1>
|
|
1498
|
+
<p class="meta">Generated by ${escapeHtml(meta.generatedBy)} at ${formatTimestamp(meta.generatedAt)}${meta.redacted ? ' (redacted)' : ''} | ${paginationInfo}</p>
|
|
1499
|
+
</header>
|
|
1500
|
+
|
|
1501
|
+
<div class="connector-info expanded">
|
|
1502
|
+
<div class="connector-info-toggle">
|
|
1503
|
+
<h2>Connector Info</h2>
|
|
1504
|
+
<span class="toggle-icon">▼</span>
|
|
1505
|
+
</div>
|
|
1506
|
+
<div class="connector-info-content">
|
|
1507
|
+
<dl>
|
|
1508
|
+
<dt>Transport</dt>
|
|
1509
|
+
<dd><span class="badge">${escapeHtml(connector.transport.type)}</span></dd>
|
|
1510
|
+
<dt>${connector.transport.type === 'stdio' ? 'Command' : 'URL'}</dt>
|
|
1511
|
+
<dd><code>${escapeHtml(transportDisplay)}</code></dd>
|
|
1512
|
+
<dt>Enabled</dt>
|
|
1513
|
+
<dd>${connector.enabled ? '<span class="badge status-OK">yes</span>' : '<span class="badge status-ERR">no</span>'}</dd>
|
|
1514
|
+
${serverInfoHtml}
|
|
1515
|
+
</dl>
|
|
1516
|
+
</div>
|
|
1517
|
+
</div>
|
|
1518
|
+
|
|
1519
|
+
<div class="main-container">
|
|
1520
|
+
<div class="sessions-pane">
|
|
1521
|
+
<div class="sessions-header">
|
|
1522
|
+
<h2>Sessions</h2>
|
|
1523
|
+
</div>
|
|
1524
|
+
<div class="sessions-list">
|
|
1525
|
+
${sessionItems}
|
|
1526
|
+
</div>
|
|
1527
|
+
</div>
|
|
1528
|
+
|
|
1529
|
+
<div class="session-detail-pane">
|
|
1530
|
+
${sessions.length === 0 ? '<div class="session-detail-empty">No sessions available</div>' : ''}
|
|
1531
|
+
${sessionContents}
|
|
1532
|
+
</div>
|
|
1533
|
+
</div>
|
|
1534
|
+
|
|
1535
|
+
<script type="application/json" id="report-data">${embeddedJson}</script>
|
|
1536
|
+
<script>${getConnectorReportScript()}</script>
|
|
1537
|
+
</body>
|
|
1538
|
+
</html>`;
|
|
1539
|
+
}
|
|
756
1540
|
//# sourceMappingURL=templates.js.map
|