@mcp-shark/mcp-shark 1.4.0

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.
Files changed (212) hide show
  1. package/LICENSE +85 -0
  2. package/README.md +724 -0
  3. package/bin/mcp-shark.js +93 -0
  4. package/mcp-server/.editorconfig +15 -0
  5. package/mcp-server/.prettierignore +11 -0
  6. package/mcp-server/.prettierrc +12 -0
  7. package/mcp-server/README.md +280 -0
  8. package/mcp-server/commitlint.config.cjs +42 -0
  9. package/mcp-server/eslint.config.js +131 -0
  10. package/mcp-server/lib/auditor/audit.js +228 -0
  11. package/mcp-server/lib/common/error.js +15 -0
  12. package/mcp-server/lib/server/external/all.js +32 -0
  13. package/mcp-server/lib/server/external/config.js +59 -0
  14. package/mcp-server/lib/server/external/kv.js +102 -0
  15. package/mcp-server/lib/server/external/single/client.js +35 -0
  16. package/mcp-server/lib/server/external/single/request.js +49 -0
  17. package/mcp-server/lib/server/external/single/run.js +75 -0
  18. package/mcp-server/lib/server/external/single/transport.js +57 -0
  19. package/mcp-server/lib/server/internal/handlers/common.js +20 -0
  20. package/mcp-server/lib/server/internal/handlers/error.js +7 -0
  21. package/mcp-server/lib/server/internal/handlers/prompts-get.js +22 -0
  22. package/mcp-server/lib/server/internal/handlers/prompts-list.js +12 -0
  23. package/mcp-server/lib/server/internal/handlers/resources-list.js +12 -0
  24. package/mcp-server/lib/server/internal/handlers/resources-read.js +19 -0
  25. package/mcp-server/lib/server/internal/handlers/tools-call.js +37 -0
  26. package/mcp-server/lib/server/internal/handlers/tools-list.js +14 -0
  27. package/mcp-server/lib/server/internal/run.js +49 -0
  28. package/mcp-server/lib/server/internal/server.js +63 -0
  29. package/mcp-server/lib/server/internal/session.js +39 -0
  30. package/mcp-server/mcp-shark.js +72 -0
  31. package/mcp-server/package-lock.json +4784 -0
  32. package/mcp-server/package.json +30 -0
  33. package/package.json +103 -0
  34. package/ui/README.md +212 -0
  35. package/ui/index.html +16 -0
  36. package/ui/package-lock.json +3574 -0
  37. package/ui/package.json +12 -0
  38. package/ui/paths.js +282 -0
  39. package/ui/public/og-image.png +0 -0
  40. package/ui/server/routes/backups.js +251 -0
  41. package/ui/server/routes/composite.js +244 -0
  42. package/ui/server/routes/config.js +175 -0
  43. package/ui/server/routes/conversations.js +25 -0
  44. package/ui/server/routes/help.js +43 -0
  45. package/ui/server/routes/logs.js +32 -0
  46. package/ui/server/routes/playground.js +152 -0
  47. package/ui/server/routes/requests.js +235 -0
  48. package/ui/server/routes/sessions.js +27 -0
  49. package/ui/server/routes/smartscan/discover.js +117 -0
  50. package/ui/server/routes/smartscan/scans/clearCache.js +22 -0
  51. package/ui/server/routes/smartscan/scans/createBatchScans.js +123 -0
  52. package/ui/server/routes/smartscan/scans/createScan.js +42 -0
  53. package/ui/server/routes/smartscan/scans/getCachedResults.js +51 -0
  54. package/ui/server/routes/smartscan/scans/getScan.js +41 -0
  55. package/ui/server/routes/smartscan/scans/listScans.js +24 -0
  56. package/ui/server/routes/smartscan/scans.js +13 -0
  57. package/ui/server/routes/smartscan/token.js +56 -0
  58. package/ui/server/routes/smartscan/transport.js +53 -0
  59. package/ui/server/routes/smartscan.js +24 -0
  60. package/ui/server/routes/statistics.js +83 -0
  61. package/ui/server/utils/config-update.js +212 -0
  62. package/ui/server/utils/config.js +98 -0
  63. package/ui/server/utils/paths.js +23 -0
  64. package/ui/server/utils/port.js +28 -0
  65. package/ui/server/utils/process.js +80 -0
  66. package/ui/server/utils/scan-cache/all-results.js +180 -0
  67. package/ui/server/utils/scan-cache/directory.js +35 -0
  68. package/ui/server/utils/scan-cache/file-operations.js +104 -0
  69. package/ui/server/utils/scan-cache/hash.js +47 -0
  70. package/ui/server/utils/scan-cache/server-operations.js +80 -0
  71. package/ui/server/utils/scan-cache.js +12 -0
  72. package/ui/server/utils/serialization.js +13 -0
  73. package/ui/server/utils/smartscan-token.js +42 -0
  74. package/ui/server.js +199 -0
  75. package/ui/src/App.jsx +153 -0
  76. package/ui/src/CompositeLogs.jsx +164 -0
  77. package/ui/src/CompositeSetup.jsx +285 -0
  78. package/ui/src/HelpGuide/HelpGuideContent.jsx +118 -0
  79. package/ui/src/HelpGuide/HelpGuideFooter.jsx +58 -0
  80. package/ui/src/HelpGuide/HelpGuideHeader.jsx +56 -0
  81. package/ui/src/HelpGuide.jsx +65 -0
  82. package/ui/src/IntroTour.jsx +140 -0
  83. package/ui/src/LogDetail.jsx +122 -0
  84. package/ui/src/LogTable.jsx +242 -0
  85. package/ui/src/PacketDetail.jsx +190 -0
  86. package/ui/src/PacketFilters.jsx +222 -0
  87. package/ui/src/PacketList.jsx +183 -0
  88. package/ui/src/SmartScan.jsx +178 -0
  89. package/ui/src/TabNavigation.jsx +143 -0
  90. package/ui/src/components/App/HelpButton.jsx +64 -0
  91. package/ui/src/components/App/TrafficTab.jsx +69 -0
  92. package/ui/src/components/App/useAppState.js +163 -0
  93. package/ui/src/components/BackupList.jsx +192 -0
  94. package/ui/src/components/CollapsibleSection.jsx +82 -0
  95. package/ui/src/components/ConfigFileSection.jsx +84 -0
  96. package/ui/src/components/ConfigViewerModal.jsx +141 -0
  97. package/ui/src/components/ConfirmationModal.jsx +129 -0
  98. package/ui/src/components/DetailsTab/BodySection.jsx +27 -0
  99. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +70 -0
  100. package/ui/src/components/DetailsTab/HeadersSection.jsx +25 -0
  101. package/ui/src/components/DetailsTab/InfoSection.jsx +28 -0
  102. package/ui/src/components/DetailsTab/NetworkInfoSection.jsx +63 -0
  103. package/ui/src/components/DetailsTab/ProtocolInfoSection.jsx +75 -0
  104. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +46 -0
  105. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +66 -0
  106. package/ui/src/components/DetailsTab.jsx +31 -0
  107. package/ui/src/components/DetectedPathsList.jsx +171 -0
  108. package/ui/src/components/FileInput.jsx +144 -0
  109. package/ui/src/components/GroupHeader.jsx +76 -0
  110. package/ui/src/components/GroupedByMcpView.jsx +103 -0
  111. package/ui/src/components/GroupedByServerView.jsx +134 -0
  112. package/ui/src/components/GroupedBySessionView.jsx +127 -0
  113. package/ui/src/components/GroupedViews.jsx +2 -0
  114. package/ui/src/components/HexTab.jsx +188 -0
  115. package/ui/src/components/LogsDisplay.jsx +93 -0
  116. package/ui/src/components/LogsToolbar.jsx +193 -0
  117. package/ui/src/components/McpPlayground/LoadingModal.jsx +113 -0
  118. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +125 -0
  119. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +48 -0
  120. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +45 -0
  121. package/ui/src/components/McpPlayground/PromptsSection.jsx +106 -0
  122. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +89 -0
  123. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +59 -0
  124. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +45 -0
  125. package/ui/src/components/McpPlayground/ResourcesSection.jsx +91 -0
  126. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +125 -0
  127. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +48 -0
  128. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +45 -0
  129. package/ui/src/components/McpPlayground/ToolsSection.jsx +107 -0
  130. package/ui/src/components/McpPlayground/common/EmptyState.jsx +17 -0
  131. package/ui/src/components/McpPlayground/common/ErrorState.jsx +17 -0
  132. package/ui/src/components/McpPlayground/common/LoadingState.jsx +17 -0
  133. package/ui/src/components/McpPlayground/useMcpPlayground.js +280 -0
  134. package/ui/src/components/McpPlayground.jsx +171 -0
  135. package/ui/src/components/MessageDisplay.jsx +28 -0
  136. package/ui/src/components/PacketDetailHeader.jsx +88 -0
  137. package/ui/src/components/PacketFilters/ExportControls.jsx +126 -0
  138. package/ui/src/components/PacketFilters/FilterInput.jsx +59 -0
  139. package/ui/src/components/RawTab.jsx +142 -0
  140. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +155 -0
  141. package/ui/src/components/RequestRow/RequestRowMain.jsx +240 -0
  142. package/ui/src/components/RequestRow/ResponseRow.jsx +158 -0
  143. package/ui/src/components/RequestRow.jsx +70 -0
  144. package/ui/src/components/ServerControl.jsx +133 -0
  145. package/ui/src/components/ServiceSelector.jsx +209 -0
  146. package/ui/src/components/SetupHeader.jsx +30 -0
  147. package/ui/src/components/SharkLogo.jsx +21 -0
  148. package/ui/src/components/SmartScan/AnalysisResult.jsx +64 -0
  149. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +215 -0
  150. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +94 -0
  151. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +26 -0
  152. package/ui/src/components/SmartScan/DebugInfoSection.jsx +53 -0
  153. package/ui/src/components/SmartScan/EmptyState.jsx +57 -0
  154. package/ui/src/components/SmartScan/ErrorDisplay.jsx +48 -0
  155. package/ui/src/components/SmartScan/ExpandableSection.jsx +93 -0
  156. package/ui/src/components/SmartScan/FindingsTable.jsx +257 -0
  157. package/ui/src/components/SmartScan/ListViewContent.jsx +75 -0
  158. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +75 -0
  159. package/ui/src/components/SmartScan/OverallSummarySection.jsx +72 -0
  160. package/ui/src/components/SmartScan/RawDataSection.jsx +52 -0
  161. package/ui/src/components/SmartScan/RecommendationsSection.jsx +78 -0
  162. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +92 -0
  163. package/ui/src/components/SmartScan/ScanDetailView.jsx +141 -0
  164. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +49 -0
  165. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +201 -0
  166. package/ui/src/components/SmartScan/ScanListView.jsx +73 -0
  167. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +123 -0
  168. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +35 -0
  169. package/ui/src/components/SmartScan/ScanViewContent.jsx +68 -0
  170. package/ui/src/components/SmartScan/ScanningProgress.jsx +47 -0
  171. package/ui/src/components/SmartScan/ServerInfoSection.jsx +43 -0
  172. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +207 -0
  173. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +269 -0
  174. package/ui/src/components/SmartScan/SmartScanControls.jsx +290 -0
  175. package/ui/src/components/SmartScan/SmartScanHeader.jsx +77 -0
  176. package/ui/src/components/SmartScan/ViewModeTabs.jsx +57 -0
  177. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +34 -0
  178. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +121 -0
  179. package/ui/src/components/SmartScan/hooks/useScanList.js +193 -0
  180. package/ui/src/components/SmartScan/hooks/useScanOperations.js +87 -0
  181. package/ui/src/components/SmartScan/hooks/useServerStatus.js +26 -0
  182. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +53 -0
  183. package/ui/src/components/SmartScan/scanDataUtils.js +98 -0
  184. package/ui/src/components/SmartScan/useSmartScan.js +72 -0
  185. package/ui/src/components/SmartScan/utils.js +19 -0
  186. package/ui/src/components/SmartScanIcons.jsx +58 -0
  187. package/ui/src/components/TabNavigation/DesktopTabs.jsx +111 -0
  188. package/ui/src/components/TabNavigation/MobileDropdown.jsx +140 -0
  189. package/ui/src/components/TabNavigation.jsx +97 -0
  190. package/ui/src/components/TabNavigationIcons.jsx +40 -0
  191. package/ui/src/components/TableHeader.jsx +164 -0
  192. package/ui/src/components/TourOverlay.jsx +117 -0
  193. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +117 -0
  194. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +70 -0
  195. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +45 -0
  196. package/ui/src/components/TourTooltip/useTooltipPosition.js +108 -0
  197. package/ui/src/components/TourTooltip.jsx +83 -0
  198. package/ui/src/components/ViewModeTabs.jsx +91 -0
  199. package/ui/src/components/WhatThisDoesSection.jsx +61 -0
  200. package/ui/src/config/tourSteps.jsx +141 -0
  201. package/ui/src/hooks/useAnimation.js +92 -0
  202. package/ui/src/hooks/useConfigManagement.js +124 -0
  203. package/ui/src/hooks/useServiceExtraction.js +51 -0
  204. package/ui/src/index.css +42 -0
  205. package/ui/src/main.jsx +10 -0
  206. package/ui/src/theme.js +65 -0
  207. package/ui/src/utils/animations.js +170 -0
  208. package/ui/src/utils/groupingUtils.js +93 -0
  209. package/ui/src/utils/hexUtils.js +24 -0
  210. package/ui/src/utils/mcpGroupingUtils.js +262 -0
  211. package/ui/src/utils/requestUtils.js +297 -0
  212. package/ui/vite.config.js +18 -0
@@ -0,0 +1,222 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { colors, fonts } from './theme';
3
+ import { fadeIn } from './utils/animations';
4
+ import FilterInput from './components/PacketFilters/FilterInput';
5
+ import ExportControls from './components/PacketFilters/ExportControls';
6
+ import ConfirmationModal from './components/ConfirmationModal';
7
+ import { IconTrash, IconSearch } from '@tabler/icons-react';
8
+ import anime from 'animejs';
9
+
10
+ function RequestFilters({ filters, onFilterChange, stats, onExport, onClear }) {
11
+ const filtersRef = useRef(null);
12
+ const [showClearModal, setShowClearModal] = useState(false);
13
+
14
+ useEffect(() => {
15
+ if (filtersRef.current) {
16
+ fadeIn(filtersRef.current, { duration: 400 });
17
+ }
18
+ }, []);
19
+
20
+ const handleExport = async (format = 'json') => {
21
+ try {
22
+ const queryParams = new URLSearchParams();
23
+ if (filters.search) queryParams.append('search', filters.search);
24
+ if (filters.serverName) queryParams.append('serverName', filters.serverName);
25
+ if (filters.sessionId) queryParams.append('sessionId', filters.sessionId);
26
+ if (filters.method) queryParams.append('method', filters.method);
27
+ if (filters.jsonrpcMethod) queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
28
+ if (filters.statusCode) queryParams.append('statusCode', filters.statusCode);
29
+ if (filters.jsonrpcId) queryParams.append('jsonrpcId', filters.jsonrpcId);
30
+ queryParams.append('format', format);
31
+
32
+ const response = await fetch(`/api/requests/export?${queryParams}`);
33
+ const blob = await response.blob();
34
+ const url = window.URL.createObjectURL(blob);
35
+ const a = document.createElement('a');
36
+ a.href = url;
37
+ const extension = format === 'csv' ? 'csv' : format === 'txt' ? 'txt' : 'json';
38
+ a.download = `mcp-shark-traffic-${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`;
39
+ document.body.appendChild(a);
40
+ a.click();
41
+ window.URL.revokeObjectURL(url);
42
+ document.body.removeChild(a);
43
+ } catch (error) {
44
+ console.error('Failed to export traffic:', error);
45
+ alert('Failed to export traffic. Please try again.');
46
+ }
47
+ };
48
+
49
+ return (
50
+ <div
51
+ ref={filtersRef}
52
+ data-tour="filters"
53
+ style={{
54
+ padding: '12px 16px',
55
+ borderBottom: `1px solid ${colors.borderLight}`,
56
+ background: colors.bgSecondary,
57
+ display: 'flex',
58
+ gap: '10px',
59
+ alignItems: 'center',
60
+ flexWrap: 'wrap',
61
+ boxShadow: `0 1px 3px ${colors.shadowSm}`,
62
+ }}
63
+ >
64
+ <div style={{ position: 'relative', width: '300px' }}>
65
+ <IconSearch
66
+ size={16}
67
+ stroke={1.5}
68
+ style={{
69
+ position: 'absolute',
70
+ left: '12px',
71
+ top: '50%',
72
+ transform: 'translateY(-50%)',
73
+ color: colors.textTertiary,
74
+ pointerEvents: 'none',
75
+ zIndex: 1,
76
+ }}
77
+ />
78
+ <FilterInput
79
+ type="text"
80
+ placeholder="Search everything (partial match)..."
81
+ value={filters.search || ''}
82
+ onChange={(e) => onFilterChange({ ...filters, search: e.target.value || null })}
83
+ style={{
84
+ width: '100%',
85
+ fontWeight: filters.search ? '500' : '400',
86
+ paddingLeft: '36px',
87
+ }}
88
+ />
89
+ </div>
90
+
91
+ <FilterInput
92
+ type="text"
93
+ placeholder="MCP Server Name..."
94
+ value={filters.serverName || ''}
95
+ onChange={(e) => onFilterChange({ ...filters, serverName: e.target.value || null })}
96
+ style={{ width: '200px' }}
97
+ />
98
+
99
+ <FilterInput
100
+ type="text"
101
+ placeholder="Session ID..."
102
+ value={filters.sessionId || ''}
103
+ onChange={(e) => onFilterChange({ ...filters, sessionId: e.target.value || null })}
104
+ style={{ width: '200px' }}
105
+ />
106
+
107
+ <FilterInput
108
+ type="text"
109
+ placeholder="HTTP Method..."
110
+ value={filters.method || ''}
111
+ onChange={(e) => onFilterChange({ ...filters, method: e.target.value || null })}
112
+ style={{ width: '150px' }}
113
+ />
114
+
115
+ <FilterInput
116
+ type="text"
117
+ placeholder="JSON-RPC Method..."
118
+ value={filters.jsonrpcMethod || ''}
119
+ onChange={(e) => onFilterChange({ ...filters, jsonrpcMethod: e.target.value || null })}
120
+ style={{ width: '200px' }}
121
+ />
122
+
123
+ <FilterInput
124
+ type="number"
125
+ placeholder="Status Code..."
126
+ value={filters.statusCode || ''}
127
+ onChange={(e) =>
128
+ onFilterChange({
129
+ ...filters,
130
+ statusCode: e.target.value ? parseInt(e.target.value) : null,
131
+ })
132
+ }
133
+ style={{ width: '120px' }}
134
+ />
135
+
136
+ <FilterInput
137
+ type="text"
138
+ placeholder="JSON-RPC ID..."
139
+ value={filters.jsonrpcId || ''}
140
+ onChange={(e) => onFilterChange({ ...filters, jsonrpcId: e.target.value || null })}
141
+ style={{ width: '150px' }}
142
+ />
143
+
144
+ <ExportControls stats={stats} onExport={handleExport} />
145
+
146
+ <button
147
+ onClick={() => setShowClearModal(true)}
148
+ style={{
149
+ padding: '8px 14px',
150
+ background: colors.buttonDanger,
151
+ border: 'none',
152
+ color: colors.textInverse,
153
+ fontSize: '12px',
154
+ fontFamily: fonts.body,
155
+ fontWeight: '500',
156
+ borderRadius: '8px',
157
+ cursor: 'pointer',
158
+ display: 'flex',
159
+ alignItems: 'center',
160
+ gap: '6px',
161
+ transition: 'all 0.2s',
162
+ boxShadow: `0 2px 4px ${colors.shadowSm}`,
163
+ marginLeft: '12px',
164
+ }}
165
+ onMouseEnter={(e) => {
166
+ anime({
167
+ targets: e.currentTarget,
168
+ background: colors.buttonDangerHover,
169
+ translateY: -1,
170
+ boxShadow: [`0 2px 4px ${colors.shadowSm}`, `0 4px 8px ${colors.shadowMd}`],
171
+ duration: 200,
172
+ easing: 'easeOutQuad',
173
+ });
174
+ }}
175
+ onMouseLeave={(e) => {
176
+ anime({
177
+ targets: e.currentTarget,
178
+ background: colors.buttonDanger,
179
+ translateY: 0,
180
+ boxShadow: [`0 4px 8px ${colors.shadowMd}`, `0 2px 4px ${colors.shadowSm}`],
181
+ duration: 200,
182
+ easing: 'easeOutQuad',
183
+ });
184
+ }}
185
+ title="Clear all captured traffic"
186
+ >
187
+ <IconTrash size={14} stroke={1.5} />
188
+ Clear
189
+ </button>
190
+
191
+ <ConfirmationModal
192
+ isOpen={showClearModal}
193
+ onClose={() => setShowClearModal(false)}
194
+ onConfirm={async () => {
195
+ try {
196
+ const response = await fetch('/api/requests/clear', {
197
+ method: 'POST',
198
+ });
199
+ if (response.ok) {
200
+ if (onClear) {
201
+ onClear();
202
+ }
203
+ } else {
204
+ const error = await response.json();
205
+ alert(`Failed to clear traffic: ${error.error || 'Unknown error'}`);
206
+ }
207
+ } catch (error) {
208
+ console.error('Failed to clear traffic:', error);
209
+ alert('Failed to clear traffic. Please try again.');
210
+ }
211
+ }}
212
+ title="Clear All Captured Traffic"
213
+ message="Are you sure you want to delete all captured traffic? This action cannot be undone and will permanently remove all requests and responses from the database."
214
+ confirmText="Clear All"
215
+ cancelText="Cancel"
216
+ danger={true}
217
+ />
218
+ </div>
219
+ );
220
+ }
221
+
222
+ export default RequestFilters;
@@ -0,0 +1,183 @@
1
+ import { useState, useEffect, useMemo, useRef } from 'react';
2
+ import { colors, fonts } from './theme';
3
+ import RequestRow from './components/RequestRow';
4
+ import TableHeader from './components/TableHeader';
5
+ import ViewModeTabs from './components/ViewModeTabs';
6
+ import GroupedByMcpView from './components/GroupedByMcpView';
7
+ import { groupByMcpSessionAndCategory } from './utils/mcpGroupingUtils.js';
8
+ import { pairRequestsWithResponses } from './utils/requestUtils.js';
9
+ import { staggerIn } from './utils/animations';
10
+ import anime from 'animejs';
11
+
12
+ function RequestList({ requests, selected, onSelect, firstRequestTime }) {
13
+ const [viewMode, setViewMode] = useState('general');
14
+ const [columnWidths] = useState({
15
+ frame: 90, // Frame numbers like "#1234"
16
+ time: 120, // Relative time like "0.123456"
17
+ datetime: 180, // Full date/time like "11/30/2024, 19:25:30"
18
+ source: 200, // IP addresses or hostnames
19
+ destination: 200, // IP addresses or hostnames
20
+ protocol: 90, // Usually "HTTP"
21
+ method: 90, // HTTP methods like "POST", "GET"
22
+ status: 80, // Status codes like "200", "404"
23
+ endpoint: 500, // JSON-RPC methods or URLs (most important, needs space)
24
+ });
25
+ const [expandedResponses, setExpandedResponses] = useState(new Set());
26
+ const [expandedMcpSessions, setExpandedMcpSessions] = useState(new Set());
27
+ const [expandedMcpCategories, setExpandedMcpCategories] = useState(new Set());
28
+ const tbodyRef = useRef(null);
29
+ const prevRequestsLengthRef = useRef(0);
30
+
31
+ const groupedByMcp = useMemo(() => groupByMcpSessionAndCategory(requests), [requests]);
32
+
33
+ // Animate rows when requests change
34
+ useEffect(() => {
35
+ if (tbodyRef.current && requests.length > 0) {
36
+ const rows = tbodyRef.current.querySelectorAll('tr');
37
+ if (rows.length > 0) {
38
+ // Only animate new rows if the list has grown
39
+ if (requests.length > prevRequestsLengthRef.current) {
40
+ const newRows = Array.from(rows).slice(prevRequestsLengthRef.current);
41
+ if (newRows.length > 0) {
42
+ staggerIn(newRows, { delay: 30, duration: 300 });
43
+ }
44
+ } else {
45
+ // Animate all rows if the list was reset
46
+ staggerIn(rows, { delay: 20, duration: 300 });
47
+ }
48
+ prevRequestsLengthRef.current = requests.length;
49
+ }
50
+ }
51
+ }, [requests]);
52
+
53
+ useEffect(() => {
54
+ if (viewMode === 'groupedByMcp') {
55
+ // Auto-expand all MCP sessions and categories
56
+ const allSessionIds = new Set(groupedByMcp.map((g) => g.sessionId || '__NO_SESSION__'));
57
+ setExpandedMcpSessions((prev) => {
58
+ const updated = new Set(prev);
59
+ allSessionIds.forEach((id) => updated.add(id));
60
+ return updated;
61
+ });
62
+
63
+ setExpandedMcpCategories((prev) => {
64
+ const updated = new Set(prev);
65
+ groupedByMcp.forEach((sessionGroup) => {
66
+ const sessionKey = sessionGroup.sessionId || '__NO_SESSION__';
67
+ sessionGroup.categories.forEach((category) => {
68
+ updated.add(`${sessionKey}::${category.category}`);
69
+ });
70
+ });
71
+ return updated;
72
+ });
73
+ }
74
+ }, [groupedByMcp, viewMode]);
75
+
76
+ const toggleMcpSession = (sessionKey) => {
77
+ setExpandedMcpSessions((prev) => {
78
+ const updated = new Set(prev);
79
+ if (updated.has(sessionKey)) {
80
+ updated.delete(sessionKey);
81
+ } else {
82
+ updated.add(sessionKey);
83
+ }
84
+ return updated;
85
+ });
86
+ };
87
+
88
+ const toggleMcpCategory = (categoryKey) => {
89
+ setExpandedMcpCategories((prev) => {
90
+ const updated = new Set(prev);
91
+ if (updated.has(categoryKey)) {
92
+ updated.delete(categoryKey);
93
+ } else {
94
+ updated.add(categoryKey);
95
+ }
96
+ return updated;
97
+ });
98
+ };
99
+
100
+ const pairedRequests = useMemo(() => pairRequestsWithResponses(requests), [requests]);
101
+
102
+ const toggleResponse = (frameNumber) => {
103
+ setExpandedResponses((prev) => {
104
+ const updated = new Set(prev);
105
+ if (updated.has(frameNumber)) {
106
+ updated.delete(frameNumber);
107
+ } else {
108
+ updated.add(frameNumber);
109
+ }
110
+ return updated;
111
+ });
112
+ };
113
+
114
+ const renderGeneralView = () => (
115
+ <tbody ref={tbodyRef}>
116
+ {pairedRequests.map((pair) => (
117
+ <RequestRow
118
+ key={pair.frame_number}
119
+ pair={pair}
120
+ selected={selected}
121
+ firstRequestTime={firstRequestTime}
122
+ onSelect={onSelect}
123
+ isExpanded={expandedResponses.has(pair.frame_number)}
124
+ onToggleExpand={() => toggleResponse(pair.frame_number)}
125
+ />
126
+ ))}
127
+ </tbody>
128
+ );
129
+
130
+ return (
131
+ <div
132
+ style={{
133
+ flex: 1,
134
+ display: 'flex',
135
+ flexDirection: 'column',
136
+ background: colors.bgPrimary,
137
+ minHeight: 0,
138
+ overflow: 'hidden',
139
+ }}
140
+ >
141
+ <ViewModeTabs viewMode={viewMode} onViewModeChange={setViewMode} />
142
+
143
+ <div
144
+ style={{
145
+ flex: 1,
146
+ overflowY: 'auto',
147
+ overflowX: 'auto',
148
+ minHeight: 0,
149
+ WebkitOverflowScrolling: 'touch',
150
+ }}
151
+ >
152
+ <table
153
+ style={{
154
+ width: '100%',
155
+ borderCollapse: 'separate',
156
+ borderSpacing: 0,
157
+ fontSize: '12px',
158
+ fontFamily: fonts.body,
159
+ background: colors.bgPrimary,
160
+ }}
161
+ >
162
+ <TableHeader columnWidths={columnWidths} />
163
+ {viewMode === 'general' ? (
164
+ renderGeneralView()
165
+ ) : (
166
+ <GroupedByMcpView
167
+ groupedData={groupedByMcp}
168
+ expandedSessions={expandedMcpSessions}
169
+ expandedCategories={expandedMcpCategories}
170
+ onToggleSession={toggleMcpSession}
171
+ onToggleCategory={toggleMcpCategory}
172
+ selected={selected}
173
+ firstRequestTime={firstRequestTime}
174
+ onSelect={onSelect}
175
+ />
176
+ )}
177
+ </table>
178
+ </div>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ export default RequestList;
@@ -0,0 +1,178 @@
1
+ import { useState } from 'react';
2
+ import { colors } from './theme';
3
+ import SmartScanHeader from './components/SmartScan/SmartScanHeader';
4
+ import SmartScanControls from './components/SmartScan/SmartScanControls';
5
+ import ViewModeTabs from './components/SmartScan/ViewModeTabs';
6
+ import ScanViewContent from './components/SmartScan/ScanViewContent';
7
+ import ListViewContent from './components/SmartScan/ListViewContent';
8
+ import { useSmartScan } from './components/SmartScan/useSmartScan';
9
+
10
+ function SmartScan() {
11
+ const [viewMode, setViewMode] = useState('scan'); // 'scan' or 'list'
12
+ const {
13
+ apiToken,
14
+ setApiToken,
15
+ discoveredServers,
16
+ selectedServers,
17
+ setSelectedServers,
18
+ loadingData,
19
+ scanning,
20
+ scanResult,
21
+ scanResults,
22
+ error,
23
+ saveToken,
24
+ discoverMcpData,
25
+ runScan,
26
+ clearCache,
27
+ clearingCache,
28
+ allScans,
29
+ loadingScans,
30
+ loadAllScans,
31
+ selectedScan,
32
+ setSelectedScan,
33
+ loadingScanDetail,
34
+ loadScanDetail,
35
+ } = useSmartScan();
36
+
37
+ return (
38
+ <div
39
+ data-tab-content
40
+ style={{
41
+ flex: 1,
42
+ overflow: 'hidden',
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ background: colors.bgPrimary,
46
+ }}
47
+ >
48
+ {/* Top Bar - Controls */}
49
+ <div
50
+ style={{
51
+ background: colors.bgCard,
52
+ borderBottom: `1px solid ${colors.borderLight}`,
53
+ padding: '16px 24px',
54
+ boxShadow: `0 2px 4px ${colors.shadowSm}`,
55
+ }}
56
+ >
57
+ <div
58
+ style={{
59
+ display: 'flex',
60
+ alignItems: 'center',
61
+ gap: '24px',
62
+ flexWrap: 'wrap',
63
+ }}
64
+ >
65
+ <SmartScanHeader />
66
+ <ViewModeTabs
67
+ viewMode={viewMode}
68
+ setViewMode={setViewMode}
69
+ onSwitchToScan={() => setSelectedScan(null)}
70
+ onSwitchToList={() => {
71
+ setSelectedScan(null);
72
+ if (allScans.length === 0) {
73
+ loadAllScans();
74
+ }
75
+ }}
76
+ />
77
+ {viewMode === 'scan' && (
78
+ <SmartScanControls
79
+ apiToken={apiToken}
80
+ setApiToken={setApiToken}
81
+ saveToken={saveToken}
82
+ loadingData={loadingData}
83
+ discoverMcpData={discoverMcpData}
84
+ discoveredServers={discoveredServers}
85
+ selectedServers={selectedServers}
86
+ setSelectedServers={setSelectedServers}
87
+ runScan={runScan}
88
+ scanning={scanning}
89
+ clearCache={clearCache}
90
+ clearingCache={clearingCache}
91
+ />
92
+ )}
93
+ </div>
94
+ </div>
95
+
96
+ {/* Content based on view mode */}
97
+ {viewMode === 'scan' ? (
98
+ <ScanViewContent
99
+ discoveredServers={discoveredServers}
100
+ selectedServers={selectedServers}
101
+ setSelectedServers={setSelectedServers}
102
+ runScan={runScan}
103
+ scanning={scanning}
104
+ apiToken={apiToken}
105
+ error={error}
106
+ scanResults={scanResults}
107
+ scanResult={scanResult}
108
+ selectedScan={selectedScan}
109
+ loadingScanDetail={loadingScanDetail}
110
+ setSelectedScan={setSelectedScan}
111
+ loadScanDetail={loadScanDetail}
112
+ onViewScan={(scanData) => {
113
+ console.log('onViewScan - scanData:', scanData);
114
+ if (scanData.scan_id && apiToken) {
115
+ loadScanDetail(scanData.scan_id);
116
+ } else if (scanData.data && typeof scanData.data === 'object') {
117
+ const actualScan = scanData.data;
118
+ setSelectedScan({
119
+ ...actualScan,
120
+ scan_id: scanData.scan_id || actualScan.id || actualScan.scan_id,
121
+ });
122
+ } else {
123
+ setSelectedScan(scanData);
124
+ }
125
+ }}
126
+ />
127
+ ) : (
128
+ <div
129
+ style={{
130
+ flex: 1,
131
+ overflowY: 'auto',
132
+ overflowX: 'hidden',
133
+ padding: '24px',
134
+ background: colors.bgPrimary,
135
+ }}
136
+ >
137
+ <ListViewContent
138
+ error={error}
139
+ loadingScans={loadingScans}
140
+ selectedScan={selectedScan}
141
+ loadingScanDetail={loadingScanDetail}
142
+ allScans={allScans}
143
+ setSelectedScan={setSelectedScan}
144
+ loadScanDetail={loadScanDetail}
145
+ onViewScan={(scanData) => {
146
+ console.log('onViewScan - scanData:', scanData);
147
+ const scanId = scanData.scan_id || scanData.id || scanData.hash;
148
+ const matchingScan = allScans.find(
149
+ (s) => s.data?.scan_id === scanId || s.data?.data?.scan_id === scanId
150
+ );
151
+ const serverName =
152
+ matchingScan?.serverName || scanData.serverName || 'Unknown Server';
153
+
154
+ if (scanData && scanData.data && typeof scanData.data === 'object') {
155
+ const actualScan = scanData.data;
156
+ setSelectedScan({
157
+ ...actualScan,
158
+ scan_id: scanId || actualScan.id || actualScan.scan_id || actualScan.hash,
159
+ serverName: serverName,
160
+ });
161
+ } else if (scanData && typeof scanData === 'object') {
162
+ setSelectedScan({
163
+ ...scanData,
164
+ scan_id: scanId || scanData.id || scanData.hash,
165
+ serverName: serverName,
166
+ });
167
+ } else {
168
+ console.warn('Invalid scanData structure:', scanData);
169
+ }
170
+ }}
171
+ />
172
+ </div>
173
+ )}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ export default SmartScan;