@mcp-shark/mcp-shark 1.4.2 → 1.5.2

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 (204) hide show
  1. package/README.md +84 -645
  2. package/bin/mcp-shark.js +30 -36
  3. package/mcp-server/index.js +115 -0
  4. package/mcp-server/lib/auditor/audit.js +22 -38
  5. package/mcp-server/lib/common/error.js +1 -1
  6. package/mcp-server/lib/server/external/all.js +5 -6
  7. package/mcp-server/lib/server/external/config.js +1 -3
  8. package/mcp-server/lib/server/external/kv.js +4 -12
  9. package/mcp-server/lib/server/external/single/request.js +3 -6
  10. package/mcp-server/lib/server/external/single/run.js +8 -19
  11. package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
  12. package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
  13. package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
  14. package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
  15. package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
  16. package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
  17. package/mcp-server/lib/server/internal/run.js +4 -16
  18. package/mcp-server/lib/server/internal/server.js +6 -7
  19. package/mcp-server/lib/server/internal/session.js +2 -15
  20. package/mcp-server/mcp-shark.js +16 -66
  21. package/package.json +23 -38
  22. package/shared/logger.js +90 -0
  23. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  24. package/ui/dist/assets/index-srLDlk97.js +35 -0
  25. package/ui/dist/index.html +17 -0
  26. package/ui/dist/og-image.png +0 -0
  27. package/ui/server/routes/backups/deleteBackup.js +54 -0
  28. package/ui/server/routes/backups/index.js +15 -0
  29. package/ui/server/routes/backups/listBackups.js +75 -0
  30. package/ui/server/routes/backups/restoreBackup.js +83 -0
  31. package/ui/server/routes/backups/viewBackup.js +47 -0
  32. package/ui/server/routes/composite/index.js +46 -0
  33. package/ui/server/routes/composite/servers.js +18 -0
  34. package/ui/server/routes/composite/setup.js +129 -0
  35. package/ui/server/routes/composite/status.js +7 -0
  36. package/ui/server/routes/composite/stop.js +39 -0
  37. package/ui/server/routes/composite/utils.js +45 -0
  38. package/ui/server/routes/config.js +34 -30
  39. package/ui/server/routes/conversations.js +3 -3
  40. package/ui/server/routes/help.js +2 -2
  41. package/ui/server/routes/logs.js +5 -5
  42. package/ui/server/routes/playground.js +45 -47
  43. package/ui/server/routes/requests.js +112 -108
  44. package/ui/server/routes/sessions.js +4 -4
  45. package/ui/server/routes/settings.js +199 -0
  46. package/ui/server/routes/smartscan/discover.js +7 -6
  47. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  48. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  49. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  51. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  52. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  53. package/ui/server/routes/smartscan/scans.js +3 -3
  54. package/ui/server/routes/smartscan/token.js +4 -3
  55. package/ui/server/routes/smartscan/transport.js +1 -1
  56. package/ui/server/routes/smartscan.js +1 -1
  57. package/ui/server/routes/statistics.js +13 -10
  58. package/ui/server/utils/config-update.js +7 -6
  59. package/ui/server/utils/config.js +4 -4
  60. package/ui/server/utils/logger.js +2 -0
  61. package/ui/server/utils/paths.js +210 -2
  62. package/ui/server/utils/port.js +2 -2
  63. package/ui/server/utils/process.js +0 -67
  64. package/ui/server/utils/scan-cache/all-results.js +76 -59
  65. package/ui/server/utils/scan-cache/directory.js +1 -1
  66. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  67. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  68. package/ui/server/utils/serialization.js +9 -3
  69. package/ui/server/utils/smartscan-token.js +4 -3
  70. package/ui/server.js +86 -41
  71. package/ui/src/App.jsx +5 -5
  72. package/ui/src/CompositeLogs.jsx +20 -20
  73. package/ui/src/CompositeSetup.jsx +9 -9
  74. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  75. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  76. package/ui/src/HelpGuide.jsx +17 -4
  77. package/ui/src/IntroTour.jsx +19 -5
  78. package/ui/src/LogDetail.jsx +1 -0
  79. package/ui/src/LogTable.jsx +24 -6
  80. package/ui/src/PacketDetail.jsx +21 -16
  81. package/ui/src/PacketFilters.jsx +29 -14
  82. package/ui/src/PacketList.jsx +4 -5
  83. package/ui/src/SmartScan.jsx +5 -5
  84. package/ui/src/TabNavigation.jsx +5 -5
  85. package/ui/src/components/App/HelpButton.jsx +4 -0
  86. package/ui/src/components/App/TrafficTab.jsx +4 -4
  87. package/ui/src/components/App/useAppState.js +118 -24
  88. package/ui/src/components/BackupList.jsx +6 -2
  89. package/ui/src/components/CollapsibleSection.jsx +16 -2
  90. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  91. package/ui/src/components/ConfirmationModal.jsx +20 -3
  92. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  93. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  94. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  95. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  97. package/ui/src/components/DetectedPathsList.jsx +5 -2
  98. package/ui/src/components/FileInput.jsx +3 -1
  99. package/ui/src/components/GroupHeader.jsx +14 -0
  100. package/ui/src/components/GroupedByMcpView.jsx +3 -4
  101. package/ui/src/components/GroupedByServerView.jsx +1 -1
  102. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  103. package/ui/src/components/HexTab.jsx +17 -4
  104. package/ui/src/components/LogsToolbar.jsx +3 -1
  105. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
  108. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
  109. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
  112. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
  113. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
  116. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
  117. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  118. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
  119. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
  120. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
  121. package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
  122. package/ui/src/components/McpPlayground.jsx +5 -2
  123. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  124. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  125. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  126. package/ui/src/components/RawTab.jsx +15 -2
  127. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  128. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  129. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  130. package/ui/src/components/RequestRow.jsx +17 -9
  131. package/ui/src/components/ServerControl.jsx +3 -1
  132. package/ui/src/components/ServiceSelector.jsx +2 -0
  133. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  136. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  137. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  138. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  139. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  140. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  141. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  142. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  143. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  144. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  145. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  146. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  147. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  148. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  149. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  150. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  151. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  152. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  153. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  154. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  155. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  156. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  157. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  158. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  159. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  160. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  161. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  162. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  163. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  164. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  165. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  166. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  167. package/ui/src/components/SmartScan/utils.js +3 -1
  168. package/ui/src/components/SmartScanIcons.jsx +6 -3
  169. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  170. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  171. package/ui/src/components/TabNavigation.jsx +8 -3
  172. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  173. package/ui/src/components/TourOverlay.jsx +1 -1
  174. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  175. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  176. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  177. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  178. package/ui/src/components/TourTooltip.jsx +11 -3
  179. package/ui/src/components/ViewModeTabs.jsx +3 -1
  180. package/ui/src/config/tourSteps.jsx +0 -2
  181. package/ui/src/hooks/useAnimation.js +15 -12
  182. package/ui/src/hooks/useConfigManagement.js +8 -8
  183. package/ui/src/hooks/useServiceExtraction.js +1 -1
  184. package/ui/src/index.css +3 -8
  185. package/ui/src/theme.js +3 -3
  186. package/ui/src/utils/hexUtils.js +11 -5
  187. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  188. package/ui/src/utils/requestPairing.js +89 -0
  189. package/ui/src/utils/requestUtils.js +32 -101
  190. package/ui/vite.config.js +1 -1
  191. package/mcp-server/.editorconfig +0 -15
  192. package/mcp-server/.prettierignore +0 -11
  193. package/mcp-server/.prettierrc +0 -12
  194. package/mcp-server/README.md +0 -280
  195. package/mcp-server/commitlint.config.cjs +0 -42
  196. package/mcp-server/eslint.config.js +0 -131
  197. package/mcp-server/package-lock.json +0 -4784
  198. package/mcp-server/package.json +0 -30
  199. package/ui/README.md +0 -212
  200. package/ui/package-lock.json +0 -3574
  201. package/ui/package.json +0 -12
  202. package/ui/paths.js +0 -282
  203. package/ui/server/routes/backups.js +0 -251
  204. package/ui/server/routes/composite.js +0 -260
@@ -1,12 +1,12 @@
1
- import { useState, useEffect, useRef } from 'react';
2
- import { colors, fonts } from './theme';
3
- import PacketDetailHeader from './components/PacketDetailHeader';
4
- import TabNavigation from './components/TabNavigation';
1
+ import { useEffect, useRef, useState } from 'react';
5
2
  import DetailsTab from './components/DetailsTab';
6
3
  import HexTab from './components/HexTab';
4
+ import PacketDetailHeader from './components/PacketDetailHeader';
7
5
  import RawTab from './components/RawTab';
8
- import { generateHexDump, createFullRequestText } from './utils/hexUtils.js';
6
+ import TabNavigation from './components/TabNavigation';
7
+ import { colors } from './theme';
9
8
  import { fadeIn } from './utils/animations';
9
+ import { createFullRequestText, generateHexDump } from './utils/hexUtils.js';
10
10
 
11
11
  function RequestDetail({ request, onClose, requests = [] }) {
12
12
  const [activeTab, setActiveTab] = useState('details');
@@ -20,7 +20,9 @@ function RequestDetail({ request, onClose, requests = [] }) {
20
20
  }
21
21
  }, [activeTab]);
22
22
 
23
- if (!request) return null;
23
+ if (!request) {
24
+ return null;
25
+ }
24
26
 
25
27
  // Helper function to extract JSON-RPC method
26
28
  const getJsonRpcMethod = (req) => {
@@ -38,7 +40,7 @@ function RequestDetail({ request, onClose, requests = [] }) {
38
40
  if (body && typeof body === 'object' && body.method) {
39
41
  return body.method;
40
42
  }
41
- } catch (e) {
43
+ } catch (_e) {
42
44
  // Failed to parse
43
45
  }
44
46
  }
@@ -48,7 +50,7 @@ function RequestDetail({ request, onClose, requests = [] }) {
48
50
  if (body && typeof body === 'object' && body.method) {
49
51
  return body.method;
50
52
  }
51
- } catch (e) {
53
+ } catch (_e) {
52
54
  // Failed to parse
53
55
  }
54
56
  }
@@ -61,7 +63,9 @@ function RequestDetail({ request, onClose, requests = [] }) {
61
63
  const findMatchingPair = () => {
62
64
  const matches = (req, resp) => {
63
65
  // Session ID must match
64
- if (req.session_id !== resp.session_id) return false;
66
+ if (req.session_id !== resp.session_id) {
67
+ return false;
68
+ }
65
69
 
66
70
  // JSON-RPC Method must match
67
71
  const reqMethod = getJsonRpcMethod(req);
@@ -78,7 +82,9 @@ function RequestDetail({ request, onClose, requests = [] }) {
78
82
  return false;
79
83
  }
80
84
 
81
- if (reqMethod !== respMethod) return false;
85
+ if (reqMethod !== respMethod) {
86
+ return false;
87
+ }
82
88
 
83
89
  // If JSON-RPC ID exists, it must match
84
90
  if (req.jsonrpc_id && resp.jsonrpc_id) {
@@ -94,13 +100,12 @@ function RequestDetail({ request, onClose, requests = [] }) {
94
100
  (r) =>
95
101
  r.direction === 'response' && matches(request, r) && r.frame_number > request.frame_number
96
102
  );
97
- } else {
98
- // Find the corresponding request
99
- return requests.find(
100
- (r) =>
101
- r.direction === 'request' && matches(r, request) && r.frame_number < request.frame_number
102
- );
103
103
  }
104
+ // Find the corresponding request
105
+ return requests.find(
106
+ (r) =>
107
+ r.direction === 'request' && matches(r, request) && r.frame_number < request.frame_number
108
+ );
104
109
  };
105
110
 
106
111
  const matchingPair = findMatchingPair();
@@ -1,13 +1,13 @@
1
+ import { IconSearch, IconTrash } from '@tabler/icons-react';
2
+ import anime from 'animejs';
1
3
  import { useEffect, useRef, useState } from 'react';
4
+ import ConfirmationModal from './components/ConfirmationModal';
5
+ import ExportControls from './components/PacketFilters/ExportControls';
6
+ import FilterInput from './components/PacketFilters/FilterInput';
2
7
  import { colors, fonts } from './theme';
3
8
  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
9
 
10
- function RequestFilters({ filters, onFilterChange, stats, onExport, onClear }) {
10
+ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
11
11
  const filtersRef = useRef(null);
12
12
  const [showClearModal, setShowClearModal] = useState(false);
13
13
 
@@ -20,13 +20,27 @@ function RequestFilters({ filters, onFilterChange, stats, onExport, onClear }) {
20
20
  const handleExport = async (format = 'json') => {
21
21
  try {
22
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);
23
+ if (filters.search) {
24
+ queryParams.append('search', filters.search);
25
+ }
26
+ if (filters.serverName) {
27
+ queryParams.append('serverName', filters.serverName);
28
+ }
29
+ if (filters.sessionId) {
30
+ queryParams.append('sessionId', filters.sessionId);
31
+ }
32
+ if (filters.method) {
33
+ queryParams.append('method', filters.method);
34
+ }
35
+ if (filters.jsonrpcMethod) {
36
+ queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
37
+ }
38
+ if (filters.statusCode) {
39
+ queryParams.append('statusCode', filters.statusCode);
40
+ }
41
+ if (filters.jsonrpcId) {
42
+ queryParams.append('jsonrpcId', filters.jsonrpcId);
43
+ }
30
44
  queryParams.append('format', format);
31
45
 
32
46
  const response = await fetch(`/api/requests/export?${queryParams}`);
@@ -127,7 +141,7 @@ function RequestFilters({ filters, onFilterChange, stats, onExport, onClear }) {
127
141
  onChange={(e) =>
128
142
  onFilterChange({
129
143
  ...filters,
130
- statusCode: e.target.value ? parseInt(e.target.value) : null,
144
+ statusCode: e.target.value ? Number.parseInt(e.target.value) : null,
131
145
  })
132
146
  }
133
147
  style={{ width: '120px' }}
@@ -144,6 +158,7 @@ function RequestFilters({ filters, onFilterChange, stats, onExport, onClear }) {
144
158
  <ExportControls stats={stats} onExport={handleExport} />
145
159
 
146
160
  <button
161
+ type="button"
147
162
  onClick={() => setShowClearModal(true)}
148
163
  style={{
149
164
  padding: '8px 14px',
@@ -1,13 +1,12 @@
1
- import { useState, useEffect, useMemo, useRef } from 'react';
2
- import { colors, fonts } from './theme';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import GroupedByMcpView from './components/GroupedByMcpView';
3
3
  import RequestRow from './components/RequestRow';
4
4
  import TableHeader from './components/TableHeader';
5
5
  import ViewModeTabs from './components/ViewModeTabs';
6
- import GroupedByMcpView from './components/GroupedByMcpView';
6
+ import { colors, fonts } from './theme';
7
+ import { staggerIn } from './utils/animations';
7
8
  import { groupByMcpSessionAndCategory } from './utils/mcpGroupingUtils.js';
8
9
  import { pairRequestsWithResponses } from './utils/requestUtils.js';
9
- import { staggerIn } from './utils/animations';
10
- import anime from 'animejs';
11
10
 
12
11
  function RequestList({ requests, selected, onSelect, firstRequestTime }) {
13
12
  const [viewMode, setViewMode] = useState('general');
@@ -1,11 +1,11 @@
1
1
  import { useState } from 'react';
2
- import { colors } from './theme';
3
- import SmartScanHeader from './components/SmartScan/SmartScanHeader';
2
+ import ListViewContent from './components/SmartScan/ListViewContent';
3
+ import ScanViewContent from './components/SmartScan/ScanViewContent';
4
4
  import SmartScanControls from './components/SmartScan/SmartScanControls';
5
+ import SmartScanHeader from './components/SmartScan/SmartScanHeader';
5
6
  import ViewModeTabs from './components/SmartScan/ViewModeTabs';
6
- import ScanViewContent from './components/SmartScan/ScanViewContent';
7
- import ListViewContent from './components/SmartScan/ListViewContent';
8
7
  import { useSmartScan } from './components/SmartScan/useSmartScan';
8
+ import { colors } from './theme';
9
9
 
10
10
  function SmartScan() {
11
11
  const [viewMode, setViewMode] = useState('scan'); // 'scan' or 'list'
@@ -151,7 +151,7 @@ function SmartScan() {
151
151
  const serverName =
152
152
  matchingScan?.serverName || scanData.serverName || 'Unknown Server';
153
153
 
154
- if (scanData && scanData.data && typeof scanData.data === 'object') {
154
+ if (scanData?.data && typeof scanData.data === 'object') {
155
155
  const actualScan = scanData.data;
156
156
  setSelectedScan({
157
157
  ...actualScan,
@@ -1,15 +1,15 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
- import { colors, fonts } from './theme';
3
2
  import { SharkLogo } from './components/SharkLogo';
3
+ import DesktopTabs from './components/TabNavigation/DesktopTabs';
4
+ import MobileDropdown from './components/TabNavigation/MobileDropdown';
4
5
  import {
5
- NetworkIcon,
6
6
  LogsIcon,
7
- SettingsIcon,
7
+ NetworkIcon,
8
8
  PlaygroundIcon,
9
+ SettingsIcon,
9
10
  ShieldIcon,
10
11
  } from './components/TabNavigationIcons';
11
- import MobileDropdown from './components/TabNavigation/MobileDropdown';
12
- import DesktopTabs from './components/TabNavigation/DesktopTabs';
12
+ import { colors, fonts } from './theme';
13
13
 
14
14
  function TabNavigation({ activeTab, onTabChange }) {
15
15
  const tabs = [
@@ -10,7 +10,10 @@ const TourIcon = ({ size = 16, color = 'currentColor' }) => (
10
10
  strokeWidth="2"
11
11
  strokeLinecap="round"
12
12
  strokeLinejoin="round"
13
+ role="img"
14
+ aria-label="Tour icon"
13
15
  >
16
+ <title>Tour icon</title>
14
17
  <path d="M12 2L2 7l10 5 10-5-10-5z" />
15
18
  <path d="M2 17l10 5 10-5" />
16
19
  <path d="M2 12l10 5 10-5" />
@@ -20,6 +23,7 @@ const TourIcon = ({ size = 16, color = 'currentColor' }) => (
20
23
  export default function HelpButton({ onClick }) {
21
24
  return (
22
25
  <button
26
+ type="button"
23
27
  onClick={onClick}
24
28
  data-tour="help-button"
25
29
  style={{
@@ -1,9 +1,9 @@
1
- import { useRef, useEffect } from 'react';
2
- import { colors } from '../../theme';
3
- import { slideInRight } from '../../utils/animations';
4
- import RequestList from '../../PacketList';
1
+ import { useEffect, useRef } from 'react';
5
2
  import RequestDetail from '../../PacketDetail';
6
3
  import RequestFilters from '../../PacketFilters';
4
+ import RequestList from '../../PacketList';
5
+ import { colors } from '../../theme';
6
+ import { slideInRight } from '../../utils/animations';
7
7
 
8
8
  export default function TrafficTab({
9
9
  requests,
@@ -1,4 +1,28 @@
1
- import { useState, useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ function appendFilterParams(queryParams, filters) {
4
+ if (filters.search) {
5
+ queryParams.append('search', filters.search);
6
+ }
7
+ if (filters.serverName) {
8
+ queryParams.append('serverName', filters.serverName);
9
+ }
10
+ if (filters.sessionId) {
11
+ queryParams.append('sessionId', filters.sessionId);
12
+ }
13
+ if (filters.method) {
14
+ queryParams.append('method', filters.method);
15
+ }
16
+ if (filters.jsonrpcMethod) {
17
+ queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
18
+ }
19
+ if (filters.statusCode) {
20
+ queryParams.append('statusCode', filters.statusCode);
21
+ }
22
+ if (filters.jsonrpcId) {
23
+ queryParams.append('jsonrpcId', filters.jsonrpcId);
24
+ }
25
+ }
2
26
 
3
27
  export function useAppState() {
4
28
  const [activeTab, setActiveTab] = useState('traffic');
@@ -11,17 +35,12 @@ export function useAppState() {
11
35
  const [tourDismissed, setTourDismissed] = useState(true);
12
36
  const wsRef = useRef(null);
13
37
  const prevTabRef = useRef(activeTab);
38
+ const filtersRef = useRef(filters);
14
39
 
15
40
  const loadStatistics = async () => {
16
41
  try {
17
42
  const queryParams = new URLSearchParams();
18
- if (filters.search) queryParams.append('search', filters.search);
19
- if (filters.serverName) queryParams.append('serverName', filters.serverName);
20
- if (filters.sessionId) queryParams.append('sessionId', filters.sessionId);
21
- if (filters.method) queryParams.append('method', filters.method);
22
- if (filters.jsonrpcMethod) queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
23
- if (filters.statusCode) queryParams.append('statusCode', filters.statusCode);
24
- if (filters.jsonrpcId) queryParams.append('jsonrpcId', filters.jsonrpcId);
43
+ appendFilterParams(queryParams, filters);
25
44
 
26
45
  const statsResponse = await fetch(`/api/statistics?${queryParams}`);
27
46
  const statsData = await statsResponse.json();
@@ -34,13 +53,7 @@ export function useAppState() {
34
53
  const loadRequests = async () => {
35
54
  try {
36
55
  const queryParams = new URLSearchParams();
37
- if (filters.search) queryParams.append('search', filters.search);
38
- if (filters.serverName) queryParams.append('serverName', filters.serverName);
39
- if (filters.sessionId) queryParams.append('sessionId', filters.sessionId);
40
- if (filters.method) queryParams.append('method', filters.method);
41
- if (filters.jsonrpcMethod) queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
42
- if (filters.statusCode) queryParams.append('statusCode', filters.statusCode);
43
- if (filters.jsonrpcId) queryParams.append('jsonrpcId', filters.jsonrpcId);
56
+ appendFilterParams(queryParams, filters);
44
57
  queryParams.append('limit', '5000');
45
58
 
46
59
  const response = await fetch(`/api/requests?${queryParams}`);
@@ -81,8 +94,37 @@ export function useAppState() {
81
94
  }
82
95
  };
83
96
 
97
+ const initData = async () => {
98
+ try {
99
+ const queryParams = new URLSearchParams();
100
+ appendFilterParams(queryParams, filters);
101
+ queryParams.append('limit', '5000');
102
+
103
+ const response = await fetch(`/api/requests?${queryParams}`);
104
+ const data = await response.json();
105
+ setRequests(data);
106
+
107
+ if (data.length > 0) {
108
+ const oldest = data[data.length - 1]?.timestamp_iso;
109
+ if (oldest) {
110
+ setFirstRequestTime(oldest);
111
+ }
112
+ }
113
+
114
+ // Also load statistics
115
+ const statsQueryParams = new URLSearchParams();
116
+ appendFilterParams(statsQueryParams, filters);
117
+
118
+ const statsResponse = await fetch(`/api/statistics?${statsQueryParams}`);
119
+ const statsData = await statsResponse.json();
120
+ setStats(statsData);
121
+ } catch (error) {
122
+ console.error('Failed to load requests:', error);
123
+ }
124
+ };
125
+
84
126
  checkTourState();
85
- loadRequests();
127
+ initData();
86
128
 
87
129
  const wsUrl = import.meta.env.DEV
88
130
  ? 'ws://localhost:9853'
@@ -92,7 +134,7 @@ export function useAppState() {
92
134
  const ws = new WebSocket(wsUrl);
93
135
  wsRef.current = ws;
94
136
 
95
- ws.onmessage = (e) => {
137
+ ws.onmessage = async (e) => {
96
138
  const msg = JSON.parse(e.data);
97
139
  if (msg.type === 'update') {
98
140
  setRequests(msg.data);
@@ -103,7 +145,16 @@ export function useAppState() {
103
145
  }
104
146
  }
105
147
  // Update statistics when new data arrives via WebSocket
106
- loadStatistics();
148
+ try {
149
+ const queryParams = new URLSearchParams();
150
+ appendFilterParams(queryParams, filters);
151
+
152
+ const statsResponse = await fetch(`/api/statistics?${queryParams}`);
153
+ const statsData = await statsResponse.json();
154
+ setStats(statsData);
155
+ } catch (error) {
156
+ console.error('Failed to load statistics:', error);
157
+ }
107
158
  }
108
159
  };
109
160
 
@@ -114,7 +165,7 @@ export function useAppState() {
114
165
  ws.onclose = () => {
115
166
  // Connection closed - will attempt to reconnect on next mount if needed
116
167
  };
117
- } catch (err) {
168
+ } catch (_err) {
118
169
  // Silently handle WebSocket creation errors
119
170
  }
120
171
 
@@ -123,10 +174,44 @@ export function useAppState() {
123
174
  wsRef.current.close();
124
175
  }
125
176
  };
126
- }, []);
177
+ }, [filters]);
178
+
179
+ useEffect(() => {
180
+ const fetchData = async () => {
181
+ try {
182
+ const queryParams = new URLSearchParams();
183
+ appendFilterParams(queryParams, filters);
184
+ queryParams.append('limit', '5000');
185
+
186
+ const response = await fetch(`/api/requests?${queryParams}`);
187
+ const data = await response.json();
188
+ setRequests(data);
189
+
190
+ if (data.length > 0) {
191
+ const oldest = data[data.length - 1]?.timestamp_iso;
192
+ if (oldest) {
193
+ setFirstRequestTime(oldest);
194
+ }
195
+ }
196
+
197
+ // Also load statistics
198
+ const statsQueryParams = new URLSearchParams();
199
+ appendFilterParams(statsQueryParams, filters);
200
+
201
+ const statsResponse = await fetch(`/api/statistics?${statsQueryParams}`);
202
+ const statsData = await statsResponse.json();
203
+ setStats(statsData);
204
+ } catch (error) {
205
+ console.error('Failed to load requests:', error);
206
+ }
207
+ };
208
+
209
+ fetchData();
210
+ }, [filters]);
127
211
 
212
+ // Keep filters ref up to date
128
213
  useEffect(() => {
129
- loadRequests();
214
+ filtersRef.current = filters;
130
215
  }, [filters]);
131
216
 
132
217
  // Periodically update statistics when on traffic tab
@@ -136,12 +221,21 @@ export function useAppState() {
136
221
  }
137
222
 
138
223
  // Update statistics every 2 seconds
139
- const interval = setInterval(() => {
140
- loadStatistics();
224
+ const interval = setInterval(async () => {
225
+ try {
226
+ const queryParams = new URLSearchParams();
227
+ appendFilterParams(queryParams, filtersRef.current);
228
+
229
+ const statsResponse = await fetch(`/api/statistics?${queryParams}`);
230
+ const statsData = await statsResponse.json();
231
+ setStats(statsData);
232
+ } catch (error) {
233
+ console.error('Failed to load statistics:', error);
234
+ }
141
235
  }, 2000);
142
236
 
143
237
  return () => clearInterval(interval);
144
- }, [activeTab, filters]);
238
+ }, [activeTab]);
145
239
 
146
240
  return {
147
241
  activeTab,
@@ -1,5 +1,5 @@
1
+ import { IconEye, IconRefresh, IconRestore, IconTrash } from '@tabler/icons-react';
1
2
  import { colors, fonts, withOpacity } from '../theme';
2
- import { IconRefresh, IconEye, IconTrash, IconRestore } from '@tabler/icons-react';
3
3
 
4
4
  function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onDelete }) {
5
5
  if (backups.length === 0) {
@@ -36,6 +36,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
36
36
  Backup Files
37
37
  </h3>
38
38
  <button
39
+ type="button"
39
40
  onClick={onRefresh}
40
41
  disabled={loadingBackups}
41
42
  style={{
@@ -68,7 +69,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
68
69
  <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
69
70
  {backups.map((backup, idx) => (
70
71
  <div
71
- key={idx}
72
+ key={backup.backupPath || `backup-${idx}`}
72
73
  style={{
73
74
  padding: '12px',
74
75
  background: colors.bgPrimary,
@@ -97,6 +98,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
97
98
  </div>
98
99
  <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
99
100
  <button
101
+ type="button"
100
102
  onClick={() => onView(backup.backupPath)}
101
103
  style={{
102
104
  padding: '6px 12px',
@@ -122,6 +124,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
122
124
  View
123
125
  </button>
124
126
  <button
127
+ type="button"
125
128
  onClick={() => {
126
129
  if (confirm('Are you sure you want to delete this backup?')) {
127
130
  onDelete(backup.backupPath);
@@ -155,6 +158,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
155
158
  Delete
156
159
  </button>
157
160
  <button
161
+ type="button"
158
162
  onClick={() => onRestore(backup.backupPath, backup.originalPath)}
159
163
  style={{
160
164
  padding: '6px 12px',
@@ -12,7 +12,10 @@ const ChevronDown = ({ size = 12, color = 'currentColor' }) => (
12
12
  strokeLinecap="round"
13
13
  strokeLinejoin="round"
14
14
  style={{ display: 'inline-block', verticalAlign: 'middle', marginRight: '4px' }}
15
+ role="img"
16
+ aria-label="Chevron icon"
15
17
  >
18
+ <title>Chevron icon</title>
16
19
  <polyline points="6 9 12 15 18 9" />
17
20
  </svg>
18
21
  );
@@ -27,7 +30,8 @@ function CollapsibleSection({
27
30
 
28
31
  return (
29
32
  <div style={{ marginBottom: '16px' }}>
30
- <div
33
+ <button
34
+ type="button"
31
35
  style={{
32
36
  color: titleColor,
33
37
  fontWeight: '600',
@@ -41,8 +45,18 @@ function CollapsibleSection({
41
45
  gap: '6px',
42
46
  padding: '4px 0',
43
47
  transition: 'color 0.15s ease',
48
+ background: 'transparent',
49
+ border: 'none',
50
+ textAlign: 'left',
51
+ width: '100%',
44
52
  }}
45
53
  onClick={() => setIsExpanded(!isExpanded)}
54
+ onKeyDown={(e) => {
55
+ if (e.key === 'Enter' || e.key === ' ') {
56
+ e.preventDefault();
57
+ setIsExpanded(!isExpanded);
58
+ }
59
+ }}
46
60
  onMouseEnter={(e) => {
47
61
  e.currentTarget.style.color =
48
62
  titleColor === colors.accentBlue ? colors.accentBlueHover : titleColor;
@@ -61,7 +75,7 @@ function CollapsibleSection({
61
75
  <ChevronDown size={12} color={titleColor} />
62
76
  </span>
63
77
  {title}
64
- </div>
78
+ </button>
65
79
  {isExpanded && (
66
80
  <div
67
81
  style={{
@@ -1,4 +1,4 @@
1
- import { colors, fonts } from '../theme';
1
+ import { colors } from '../theme';
2
2
 
3
3
  function ConfigViewerModal({
4
4
  viewingConfig,
@@ -21,7 +21,9 @@ function ConfigViewerModal({
21
21
  const title = isViewingBackup ? 'Backup File' : 'MCP Configuration File';
22
22
 
23
23
  return (
24
- <div
24
+ <dialog
25
+ open
26
+ aria-modal="true"
25
27
  style={{
26
28
  position: 'fixed',
27
29
  top: 0,
@@ -34,10 +36,20 @@ function ConfigViewerModal({
34
36
  justifyContent: 'center',
35
37
  zIndex: 1000,
36
38
  padding: '20px',
39
+ border: 'none',
40
+ margin: 0,
41
+ width: '100%',
42
+ height: '100%',
37
43
  }}
38
44
  onClick={onClose}
45
+ onKeyDown={(e) => {
46
+ if (e.key === 'Escape') {
47
+ onClose();
48
+ }
49
+ }}
39
50
  >
40
51
  <div
52
+ role="document"
41
53
  style={{
42
54
  background: colors.bgPrimary,
43
55
  border: `1px solid ${colors.borderLight}`,
@@ -50,6 +62,7 @@ function ConfigViewerModal({
50
62
  overflow: 'hidden',
51
63
  }}
52
64
  onClick={(e) => e.stopPropagation()}
65
+ onKeyDown={(e) => e.stopPropagation()}
53
66
  >
54
67
  <div
55
68
  style={{
@@ -83,6 +96,7 @@ function ConfigViewerModal({
83
96
  )}
84
97
  </div>
85
98
  <button
99
+ type="button"
86
100
  onClick={onClose}
87
101
  style={{
88
102
  background: 'transparent',
@@ -134,7 +148,7 @@ function ConfigViewerModal({
134
148
  )}
135
149
  </div>
136
150
  </div>
137
- </div>
151
+ </dialog>
138
152
  );
139
153
  }
140
154