@mcp-shark/mcp-shark 1.4.2 → 1.5.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.
- package/README.md +84 -645
- package/bin/mcp-shark.js +30 -36
- package/mcp-server/index.js +115 -0
- package/mcp-server/lib/auditor/audit.js +22 -38
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +4 -12
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
- package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
- package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
- package/mcp-server/lib/server/internal/run.js +4 -16
- package/mcp-server/lib/server/internal/server.js +6 -7
- package/mcp-server/lib/server/internal/session.js +2 -15
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +45 -47
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +7 -6
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +86 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -4
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
- package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
- package/ui/src/components/McpPlayground.jsx +5 -2
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +32 -101
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -260
package/ui/src/PacketDetail.jsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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)
|
|
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 (
|
|
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 (
|
|
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)
|
|
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)
|
|
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();
|
package/ui/src/PacketFilters.jsx
CHANGED
|
@@ -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,
|
|
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)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (filters.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (filters.
|
|
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',
|
package/ui/src/PacketList.jsx
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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
|
|
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');
|
package/ui/src/SmartScan.jsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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
|
|
154
|
+
if (scanData?.data && typeof scanData.data === 'object') {
|
|
155
155
|
const actualScan = scanData.data;
|
|
156
156
|
setSelectedScan({
|
|
157
157
|
...actualScan,
|
package/ui/src/TabNavigation.jsx
CHANGED
|
@@ -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
|
-
|
|
7
|
+
NetworkIcon,
|
|
8
8
|
PlaygroundIcon,
|
|
9
|
+
SettingsIcon,
|
|
9
10
|
ShieldIcon,
|
|
10
11
|
} from './components/TabNavigationIcons';
|
|
11
|
-
import
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
78
|
+
</button>
|
|
65
79
|
{isExpanded && (
|
|
66
80
|
<div
|
|
67
81
|
style={{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { colors
|
|
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
|
-
<
|
|
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
|
-
</
|
|
151
|
+
</dialog>
|
|
138
152
|
);
|
|
139
153
|
}
|
|
140
154
|
|