@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.
- package/LICENSE +85 -0
- package/README.md +724 -0
- package/bin/mcp-shark.js +93 -0
- package/mcp-server/.editorconfig +15 -0
- package/mcp-server/.prettierignore +11 -0
- package/mcp-server/.prettierrc +12 -0
- package/mcp-server/README.md +280 -0
- package/mcp-server/commitlint.config.cjs +42 -0
- package/mcp-server/eslint.config.js +131 -0
- package/mcp-server/lib/auditor/audit.js +228 -0
- package/mcp-server/lib/common/error.js +15 -0
- package/mcp-server/lib/server/external/all.js +32 -0
- package/mcp-server/lib/server/external/config.js +59 -0
- package/mcp-server/lib/server/external/kv.js +102 -0
- package/mcp-server/lib/server/external/single/client.js +35 -0
- package/mcp-server/lib/server/external/single/request.js +49 -0
- package/mcp-server/lib/server/external/single/run.js +75 -0
- package/mcp-server/lib/server/external/single/transport.js +57 -0
- package/mcp-server/lib/server/internal/handlers/common.js +20 -0
- package/mcp-server/lib/server/internal/handlers/error.js +7 -0
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +22 -0
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +12 -0
- package/mcp-server/lib/server/internal/handlers/resources-list.js +12 -0
- package/mcp-server/lib/server/internal/handlers/resources-read.js +19 -0
- package/mcp-server/lib/server/internal/handlers/tools-call.js +37 -0
- package/mcp-server/lib/server/internal/handlers/tools-list.js +14 -0
- package/mcp-server/lib/server/internal/run.js +49 -0
- package/mcp-server/lib/server/internal/server.js +63 -0
- package/mcp-server/lib/server/internal/session.js +39 -0
- package/mcp-server/mcp-shark.js +72 -0
- package/mcp-server/package-lock.json +4784 -0
- package/mcp-server/package.json +30 -0
- package/package.json +103 -0
- package/ui/README.md +212 -0
- package/ui/index.html +16 -0
- package/ui/package-lock.json +3574 -0
- package/ui/package.json +12 -0
- package/ui/paths.js +282 -0
- package/ui/public/og-image.png +0 -0
- package/ui/server/routes/backups.js +251 -0
- package/ui/server/routes/composite.js +244 -0
- package/ui/server/routes/config.js +175 -0
- package/ui/server/routes/conversations.js +25 -0
- package/ui/server/routes/help.js +43 -0
- package/ui/server/routes/logs.js +32 -0
- package/ui/server/routes/playground.js +152 -0
- package/ui/server/routes/requests.js +235 -0
- package/ui/server/routes/sessions.js +27 -0
- package/ui/server/routes/smartscan/discover.js +117 -0
- package/ui/server/routes/smartscan/scans/clearCache.js +22 -0
- package/ui/server/routes/smartscan/scans/createBatchScans.js +123 -0
- package/ui/server/routes/smartscan/scans/createScan.js +42 -0
- package/ui/server/routes/smartscan/scans/getCachedResults.js +51 -0
- package/ui/server/routes/smartscan/scans/getScan.js +41 -0
- package/ui/server/routes/smartscan/scans/listScans.js +24 -0
- package/ui/server/routes/smartscan/scans.js +13 -0
- package/ui/server/routes/smartscan/token.js +56 -0
- package/ui/server/routes/smartscan/transport.js +53 -0
- package/ui/server/routes/smartscan.js +24 -0
- package/ui/server/routes/statistics.js +83 -0
- package/ui/server/utils/config-update.js +212 -0
- package/ui/server/utils/config.js +98 -0
- package/ui/server/utils/paths.js +23 -0
- package/ui/server/utils/port.js +28 -0
- package/ui/server/utils/process.js +80 -0
- package/ui/server/utils/scan-cache/all-results.js +180 -0
- package/ui/server/utils/scan-cache/directory.js +35 -0
- package/ui/server/utils/scan-cache/file-operations.js +104 -0
- package/ui/server/utils/scan-cache/hash.js +47 -0
- package/ui/server/utils/scan-cache/server-operations.js +80 -0
- package/ui/server/utils/scan-cache.js +12 -0
- package/ui/server/utils/serialization.js +13 -0
- package/ui/server/utils/smartscan-token.js +42 -0
- package/ui/server.js +199 -0
- package/ui/src/App.jsx +153 -0
- package/ui/src/CompositeLogs.jsx +164 -0
- package/ui/src/CompositeSetup.jsx +285 -0
- package/ui/src/HelpGuide/HelpGuideContent.jsx +118 -0
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +58 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +56 -0
- package/ui/src/HelpGuide.jsx +65 -0
- package/ui/src/IntroTour.jsx +140 -0
- package/ui/src/LogDetail.jsx +122 -0
- package/ui/src/LogTable.jsx +242 -0
- package/ui/src/PacketDetail.jsx +190 -0
- package/ui/src/PacketFilters.jsx +222 -0
- package/ui/src/PacketList.jsx +183 -0
- package/ui/src/SmartScan.jsx +178 -0
- package/ui/src/TabNavigation.jsx +143 -0
- package/ui/src/components/App/HelpButton.jsx +64 -0
- package/ui/src/components/App/TrafficTab.jsx +69 -0
- package/ui/src/components/App/useAppState.js +163 -0
- package/ui/src/components/BackupList.jsx +192 -0
- package/ui/src/components/CollapsibleSection.jsx +82 -0
- package/ui/src/components/ConfigFileSection.jsx +84 -0
- package/ui/src/components/ConfigViewerModal.jsx +141 -0
- package/ui/src/components/ConfirmationModal.jsx +129 -0
- package/ui/src/components/DetailsTab/BodySection.jsx +27 -0
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +70 -0
- package/ui/src/components/DetailsTab/HeadersSection.jsx +25 -0
- package/ui/src/components/DetailsTab/InfoSection.jsx +28 -0
- package/ui/src/components/DetailsTab/NetworkInfoSection.jsx +63 -0
- package/ui/src/components/DetailsTab/ProtocolInfoSection.jsx +75 -0
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +46 -0
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +66 -0
- package/ui/src/components/DetailsTab.jsx +31 -0
- package/ui/src/components/DetectedPathsList.jsx +171 -0
- package/ui/src/components/FileInput.jsx +144 -0
- package/ui/src/components/GroupHeader.jsx +76 -0
- package/ui/src/components/GroupedByMcpView.jsx +103 -0
- package/ui/src/components/GroupedByServerView.jsx +134 -0
- package/ui/src/components/GroupedBySessionView.jsx +127 -0
- package/ui/src/components/GroupedViews.jsx +2 -0
- package/ui/src/components/HexTab.jsx +188 -0
- package/ui/src/components/LogsDisplay.jsx +93 -0
- package/ui/src/components/LogsToolbar.jsx +193 -0
- package/ui/src/components/McpPlayground/LoadingModal.jsx +113 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +125 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +48 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +45 -0
- package/ui/src/components/McpPlayground/PromptsSection.jsx +106 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +89 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +59 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +45 -0
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +91 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +125 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +48 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +45 -0
- package/ui/src/components/McpPlayground/ToolsSection.jsx +107 -0
- package/ui/src/components/McpPlayground/common/EmptyState.jsx +17 -0
- package/ui/src/components/McpPlayground/common/ErrorState.jsx +17 -0
- package/ui/src/components/McpPlayground/common/LoadingState.jsx +17 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +280 -0
- package/ui/src/components/McpPlayground.jsx +171 -0
- package/ui/src/components/MessageDisplay.jsx +28 -0
- package/ui/src/components/PacketDetailHeader.jsx +88 -0
- package/ui/src/components/PacketFilters/ExportControls.jsx +126 -0
- package/ui/src/components/PacketFilters/FilterInput.jsx +59 -0
- package/ui/src/components/RawTab.jsx +142 -0
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +155 -0
- package/ui/src/components/RequestRow/RequestRowMain.jsx +240 -0
- package/ui/src/components/RequestRow/ResponseRow.jsx +158 -0
- package/ui/src/components/RequestRow.jsx +70 -0
- package/ui/src/components/ServerControl.jsx +133 -0
- package/ui/src/components/ServiceSelector.jsx +209 -0
- package/ui/src/components/SetupHeader.jsx +30 -0
- package/ui/src/components/SharkLogo.jsx +21 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +64 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +215 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +94 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +26 -0
- package/ui/src/components/SmartScan/DebugInfoSection.jsx +53 -0
- package/ui/src/components/SmartScan/EmptyState.jsx +57 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +48 -0
- package/ui/src/components/SmartScan/ExpandableSection.jsx +93 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +257 -0
- package/ui/src/components/SmartScan/ListViewContent.jsx +75 -0
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +75 -0
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +72 -0
- package/ui/src/components/SmartScan/RawDataSection.jsx +52 -0
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +78 -0
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +92 -0
- package/ui/src/components/SmartScan/ScanDetailView.jsx +141 -0
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +49 -0
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +201 -0
- package/ui/src/components/SmartScan/ScanListView.jsx +73 -0
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +123 -0
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +35 -0
- package/ui/src/components/SmartScan/ScanViewContent.jsx +68 -0
- package/ui/src/components/SmartScan/ScanningProgress.jsx +47 -0
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +43 -0
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +207 -0
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +269 -0
- package/ui/src/components/SmartScan/SmartScanControls.jsx +290 -0
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +77 -0
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +57 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +34 -0
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +121 -0
- package/ui/src/components/SmartScan/hooks/useScanList.js +193 -0
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +87 -0
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +26 -0
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +53 -0
- package/ui/src/components/SmartScan/scanDataUtils.js +98 -0
- package/ui/src/components/SmartScan/useSmartScan.js +72 -0
- package/ui/src/components/SmartScan/utils.js +19 -0
- package/ui/src/components/SmartScanIcons.jsx +58 -0
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +111 -0
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +140 -0
- package/ui/src/components/TabNavigation.jsx +97 -0
- package/ui/src/components/TabNavigationIcons.jsx +40 -0
- package/ui/src/components/TableHeader.jsx +164 -0
- package/ui/src/components/TourOverlay.jsx +117 -0
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +117 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +70 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +45 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +108 -0
- package/ui/src/components/TourTooltip.jsx +83 -0
- package/ui/src/components/ViewModeTabs.jsx +91 -0
- package/ui/src/components/WhatThisDoesSection.jsx +61 -0
- package/ui/src/config/tourSteps.jsx +141 -0
- package/ui/src/hooks/useAnimation.js +92 -0
- package/ui/src/hooks/useConfigManagement.js +124 -0
- package/ui/src/hooks/useServiceExtraction.js +51 -0
- package/ui/src/index.css +42 -0
- package/ui/src/main.jsx +10 -0
- package/ui/src/theme.js +65 -0
- package/ui/src/utils/animations.js +170 -0
- package/ui/src/utils/groupingUtils.js +93 -0
- package/ui/src/utils/hexUtils.js +24 -0
- package/ui/src/utils/mcpGroupingUtils.js +262 -0
- package/ui/src/utils/requestUtils.js +297 -0
- package/ui/vite.config.js +18 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useScanList(apiToken, setError) {
|
|
4
|
+
const [allScans, setAllScans] = useState([]);
|
|
5
|
+
const [loadingScans, setLoadingScans] = useState(false);
|
|
6
|
+
const [selectedScan, setSelectedScan] = useState(null);
|
|
7
|
+
const [loadingScanDetail, setLoadingScanDetail] = useState(false);
|
|
8
|
+
|
|
9
|
+
const loadAllScans = async () => {
|
|
10
|
+
setLoadingScans(true);
|
|
11
|
+
setError(null);
|
|
12
|
+
try {
|
|
13
|
+
// Only load from local cache
|
|
14
|
+
console.log('Loading cached scans from local storage...');
|
|
15
|
+
const cacheResponse = await fetch('/api/smartscan/scans?cache=true');
|
|
16
|
+
const cacheData = await cacheResponse.json();
|
|
17
|
+
console.log('Cache response:', {
|
|
18
|
+
status: cacheResponse.status,
|
|
19
|
+
ok: cacheResponse.ok,
|
|
20
|
+
cacheData,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (cacheResponse.ok) {
|
|
24
|
+
const scans = cacheData.scans || [];
|
|
25
|
+
console.log(`[useScanList] Loaded ${scans.length} cached scans from API`);
|
|
26
|
+
console.log(`[useScanList] Full cacheData:`, cacheData);
|
|
27
|
+
|
|
28
|
+
// Debug: Log first scan structure to see what we're receiving
|
|
29
|
+
if (scans.length > 0) {
|
|
30
|
+
console.log('[useScanList] First scan structure:', {
|
|
31
|
+
keys: Object.keys(scans[0]),
|
|
32
|
+
serverName: scans[0].serverName,
|
|
33
|
+
server_name: scans[0].server_name,
|
|
34
|
+
server: scans[0].server,
|
|
35
|
+
fullScan: scans[0],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Transform cached scans to BatchResultsDisplay format
|
|
40
|
+
const scanResults = scans.map((scan, index) => {
|
|
41
|
+
// The scan structure from getAllCachedScanResults has:
|
|
42
|
+
// { id, scan_id, server: { name }, serverName, server_name, data, result, ... }
|
|
43
|
+
|
|
44
|
+
console.log(`[useScanList] Transforming scan ${index}:`, {
|
|
45
|
+
scanId: scan.id || scan.scan_id,
|
|
46
|
+
serverName: scan.serverName,
|
|
47
|
+
server_name: scan.server_name,
|
|
48
|
+
server: scan.server,
|
|
49
|
+
hasServerName: !!scan.serverName,
|
|
50
|
+
hasServerNameAlt: !!scan.server_name,
|
|
51
|
+
hasServer: !!scan.server,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Extract server name from multiple possible locations
|
|
55
|
+
// Handle empty strings, null, undefined
|
|
56
|
+
const serverName =
|
|
57
|
+
(scan.serverName && scan.serverName.trim()) ||
|
|
58
|
+
(scan.server_name && scan.server_name.trim()) ||
|
|
59
|
+
(scan.server?.name && scan.server.name.trim()) ||
|
|
60
|
+
'Unknown Server';
|
|
61
|
+
|
|
62
|
+
console.log(`[useScanList] Extracted serverName for scan ${index}: "${serverName}"`);
|
|
63
|
+
|
|
64
|
+
// Debug logging
|
|
65
|
+
if (serverName === 'Unknown Server') {
|
|
66
|
+
console.error('[useScanList] Could not find server name in scan:', {
|
|
67
|
+
scanId: scan.id || scan.scan_id,
|
|
68
|
+
hasServerName: !!scan.serverName,
|
|
69
|
+
hasServerNameAlt: !!scan.server_name,
|
|
70
|
+
hasServer: !!scan.server,
|
|
71
|
+
serverNameValue: scan.serverName,
|
|
72
|
+
serverNameAltValue: scan.server_name,
|
|
73
|
+
serverNameFromServer: scan.server?.name,
|
|
74
|
+
scanKeys: Object.keys(scan),
|
|
75
|
+
fullScanObject: scan,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Get the actual scan data - it might be nested
|
|
80
|
+
// scan.data could be the scan result from API which has { success, data, scan_id, ... }
|
|
81
|
+
// or it could be the direct scan data
|
|
82
|
+
let scanData = scan.data || scan.result || scan;
|
|
83
|
+
|
|
84
|
+
// If scanData has a nested 'data' property (from API response), use that
|
|
85
|
+
if (scanData && scanData.data && typeof scanData.data === 'object') {
|
|
86
|
+
scanData = scanData.data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const transformed = {
|
|
90
|
+
serverName: serverName,
|
|
91
|
+
success: true,
|
|
92
|
+
cached: true,
|
|
93
|
+
data: {
|
|
94
|
+
scan_id: scan.scan_id || scan.id,
|
|
95
|
+
data: scanData,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
console.log(`[useScanList] Transformed result ${index}:`, {
|
|
100
|
+
serverName: transformed.serverName,
|
|
101
|
+
scan_id: transformed.data.scan_id,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return transformed;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log(
|
|
108
|
+
'[useScanList] Final transformed scanResults:',
|
|
109
|
+
scanResults.map((r) => ({
|
|
110
|
+
serverName: r.serverName,
|
|
111
|
+
scan_id: r.data.scan_id,
|
|
112
|
+
}))
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
console.log('[useScanList] Setting allScans state with:', scanResults.length, 'items');
|
|
116
|
+
console.log('[useScanList] First item serverName:', scanResults[0]?.serverName);
|
|
117
|
+
console.log('[useScanList] Second item serverName:', scanResults[1]?.serverName);
|
|
118
|
+
|
|
119
|
+
setAllScans(scanResults);
|
|
120
|
+
if (scanResults.length === 0) {
|
|
121
|
+
setError('No cached scans found. Please run a scan first to see results here.');
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
setError(cacheData.error || cacheData.message || 'Failed to load cached scans');
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
setError(err.message || 'Failed to load cached scans');
|
|
128
|
+
} finally {
|
|
129
|
+
setLoadingScans(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const loadScanDetail = async (scanId) => {
|
|
134
|
+
if (!scanId) {
|
|
135
|
+
setSelectedScan(null);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// First, check if we already have this scan in allScans (cached scan)
|
|
140
|
+
// allScans is in BatchResultsDisplay format: { serverName, success, cached, data: { scan_id, data: {...} } }
|
|
141
|
+
const cachedResult = allScans.find(
|
|
142
|
+
(r) =>
|
|
143
|
+
r.data?.scan_id === scanId ||
|
|
144
|
+
r.data?.data?.id === scanId ||
|
|
145
|
+
r.data?.data?.scan_id === scanId
|
|
146
|
+
);
|
|
147
|
+
if (cachedResult && cachedResult.cached && cachedResult.data?.data) {
|
|
148
|
+
// Use the cached scan data directly
|
|
149
|
+
const scanData = cachedResult.data.data;
|
|
150
|
+
setSelectedScan({
|
|
151
|
+
...scanData,
|
|
152
|
+
scan_id: cachedResult.data.scan_id || scanData.id || scanData.scan_id,
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If not cached and no API token, can't load from API
|
|
158
|
+
if (!apiToken) {
|
|
159
|
+
setError('Please enter your API token to view scan details');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setLoadingScanDetail(true);
|
|
164
|
+
setError(null);
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(`/api/smartscan/scans/${scanId}`, {
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: `Bearer ${apiToken}`,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
if (response.ok) {
|
|
173
|
+
setSelectedScan(data.result || data);
|
|
174
|
+
} else {
|
|
175
|
+
setError(data.error || data.message || 'Failed to load scan details');
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
setError(err.message || 'Failed to load scan details');
|
|
179
|
+
} finally {
|
|
180
|
+
setLoadingScanDetail(false);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
allScans,
|
|
186
|
+
loadingScans,
|
|
187
|
+
loadAllScans,
|
|
188
|
+
selectedScan,
|
|
189
|
+
setSelectedScan,
|
|
190
|
+
loadingScanDetail,
|
|
191
|
+
loadScanDetail,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useScanOperations(apiToken, discoveredServers, selectedServers, setError) {
|
|
4
|
+
const [scanning, setScanning] = useState(false);
|
|
5
|
+
const [scanResult, setScanResult] = useState(null);
|
|
6
|
+
const [scanResults, setScanResults] = useState([]);
|
|
7
|
+
|
|
8
|
+
const runScan = async () => {
|
|
9
|
+
if (!apiToken) {
|
|
10
|
+
setError('Please enter your API token');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!discoveredServers || discoveredServers.length === 0) {
|
|
15
|
+
setError('Please discover MCP servers first');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (selectedServers.size === 0) {
|
|
20
|
+
setError('Please select at least one server to scan');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setScanning(true);
|
|
25
|
+
setError(null);
|
|
26
|
+
setScanResult(null);
|
|
27
|
+
setScanResults([]);
|
|
28
|
+
|
|
29
|
+
const serversToScan = discoveredServers.filter((server) => selectedServers.has(server.name));
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch('/api/smartscan/scans/batch', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
apiToken,
|
|
39
|
+
servers: serversToScan,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
let errorMessage = data.error || data.message || `API error: ${response.status}`;
|
|
47
|
+
|
|
48
|
+
if (response.status === 400 && data.details) {
|
|
49
|
+
if (Array.isArray(data.details)) {
|
|
50
|
+
errorMessage = `Validation failed: ${data.details
|
|
51
|
+
.map((d) => {
|
|
52
|
+
if (typeof d === 'string') return d;
|
|
53
|
+
if (d.field && d.message) return `${d.field}: ${d.message}`;
|
|
54
|
+
return JSON.stringify(d);
|
|
55
|
+
})
|
|
56
|
+
.join('; ')}`;
|
|
57
|
+
} else if (typeof data.details === 'string') {
|
|
58
|
+
errorMessage = data.details;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setError(errorMessage);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (data.results && Array.isArray(data.results)) {
|
|
67
|
+
setScanResults(data.results);
|
|
68
|
+
|
|
69
|
+
const firstSuccess = data.results.find((r) => r.success);
|
|
70
|
+
if (firstSuccess) {
|
|
71
|
+
setScanResult(firstSuccess.data);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
setError(err.message || 'Failed to run scan');
|
|
76
|
+
} finally {
|
|
77
|
+
setScanning(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
scanning,
|
|
83
|
+
scanResult,
|
|
84
|
+
scanResults,
|
|
85
|
+
runScan,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useServerStatus() {
|
|
4
|
+
const [serverStatus, setServerStatus] = useState(null);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
checkServerStatus();
|
|
8
|
+
const interval = setInterval(checkServerStatus, 2000);
|
|
9
|
+
return () => clearInterval(interval);
|
|
10
|
+
}, []);
|
|
11
|
+
|
|
12
|
+
const checkServerStatus = async () => {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch('/api/composite/status');
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error('Server not available');
|
|
17
|
+
}
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
setServerStatus(data);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
setServerStatus({ running: false });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return { serverStatus };
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useTokenManagement() {
|
|
4
|
+
const [apiToken, setApiToken] = useState('');
|
|
5
|
+
const saveTokenTimeoutRef = useRef(null);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
loadStoredToken();
|
|
9
|
+
return () => {
|
|
10
|
+
if (saveTokenTimeoutRef.current) {
|
|
11
|
+
clearTimeout(saveTokenTimeoutRef.current);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
const loadStoredToken = async () => {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch('/api/smartscan/token');
|
|
19
|
+
if (response.ok) {
|
|
20
|
+
const data = await response.json();
|
|
21
|
+
if (data.token) {
|
|
22
|
+
setApiToken(data.token);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.debug('No stored token found');
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const saveToken = async (token) => {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch('/api/smartscan/token', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ token }),
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
console.error('Failed to save token');
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('Error saving token:', err);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
apiToken,
|
|
49
|
+
setApiToken,
|
|
50
|
+
saveToken,
|
|
51
|
+
saveTokenTimeoutRef,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for normalizing scan data from various API response formats
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function getScanValue(scan, path) {
|
|
6
|
+
const paths = path.split('.');
|
|
7
|
+
let value = scan;
|
|
8
|
+
for (const p of paths) {
|
|
9
|
+
if (value && typeof value === 'object' && p in value) {
|
|
10
|
+
value = value[p];
|
|
11
|
+
} else {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizeScanData(scan) {
|
|
19
|
+
const actualScan = scan.result || scan.data || scan;
|
|
20
|
+
|
|
21
|
+
const scanId =
|
|
22
|
+
getScanValue(scan, 'result.id') ||
|
|
23
|
+
getScanValue(scan, 'id') ||
|
|
24
|
+
getScanValue(scan, 'scan_id') ||
|
|
25
|
+
getScanValue(scan, 'data.id') ||
|
|
26
|
+
getScanValue(scan, 'data.scan_id') ||
|
|
27
|
+
(actualScan && actualScan.id) ||
|
|
28
|
+
(actualScan && actualScan.scan_id);
|
|
29
|
+
|
|
30
|
+
const serverName =
|
|
31
|
+
scan.serverName || // Check top-level first (for cached scans)
|
|
32
|
+
scan.server_name ||
|
|
33
|
+
getScanValue(scan, 'result.mcp_server_data.server.name') ||
|
|
34
|
+
getScanValue(scan, 'mcp_server_data.server.name') ||
|
|
35
|
+
getScanValue(scan, 'server.name') ||
|
|
36
|
+
getScanValue(scan, 'data.server.name') ||
|
|
37
|
+
getScanValue(scan, 'data.data.server.name') ||
|
|
38
|
+
(scan.server && scan.server.name) || // Check nested server object
|
|
39
|
+
'Unknown Server';
|
|
40
|
+
|
|
41
|
+
const status =
|
|
42
|
+
getScanValue(scan, 'result.status') ||
|
|
43
|
+
getScanValue(scan, 'status') ||
|
|
44
|
+
getScanValue(scan, 'data.status');
|
|
45
|
+
|
|
46
|
+
const overallRiskLevel =
|
|
47
|
+
getScanValue(scan, 'result.overall_risk_level') ||
|
|
48
|
+
getScanValue(scan, 'overall_risk_level') ||
|
|
49
|
+
getScanValue(scan, 'data.overall_risk_level') ||
|
|
50
|
+
getScanValue(scan, 'data.data.overall_risk_level');
|
|
51
|
+
|
|
52
|
+
const createdAt =
|
|
53
|
+
getScanValue(scan, 'result.created_at') ||
|
|
54
|
+
getScanValue(scan, 'created_at') ||
|
|
55
|
+
getScanValue(scan, 'data.created_at') ||
|
|
56
|
+
getScanValue(scan, 'data.data.created_at');
|
|
57
|
+
|
|
58
|
+
const updatedAt =
|
|
59
|
+
getScanValue(scan, 'result.updated_at') ||
|
|
60
|
+
getScanValue(scan, 'updated_at') ||
|
|
61
|
+
getScanValue(scan, 'data.updated_at') ||
|
|
62
|
+
getScanValue(scan, 'data.data.updated_at');
|
|
63
|
+
|
|
64
|
+
let analysisResult =
|
|
65
|
+
getScanValue(scan, 'result.analysis_result') ||
|
|
66
|
+
getScanValue(scan, 'analysis_result') ||
|
|
67
|
+
getScanValue(scan, 'data.analysis_result') ||
|
|
68
|
+
getScanValue(scan, 'data.data.analysis_result') ||
|
|
69
|
+
getScanValue(scan, 'data.data.data.analysis_result');
|
|
70
|
+
|
|
71
|
+
if (!analysisResult && actualScan && typeof actualScan === 'object') {
|
|
72
|
+
if (actualScan.tool_findings || actualScan.prompt_findings || actualScan.resource_findings) {
|
|
73
|
+
analysisResult = actualScan;
|
|
74
|
+
}
|
|
75
|
+
if (!analysisResult && actualScan.analysis_result) {
|
|
76
|
+
analysisResult = actualScan.analysis_result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const serverData =
|
|
81
|
+
getScanValue(scan, 'result.mcp_server_data.server') ||
|
|
82
|
+
getScanValue(scan, 'mcp_server_data.server') ||
|
|
83
|
+
getScanValue(scan, 'server') ||
|
|
84
|
+
getScanValue(scan, 'data.server') ||
|
|
85
|
+
getScanValue(scan, 'data.data.server') ||
|
|
86
|
+
getScanValue(scan, 'mcp_server_data');
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
scanId,
|
|
90
|
+
serverName,
|
|
91
|
+
status,
|
|
92
|
+
overallRiskLevel,
|
|
93
|
+
createdAt,
|
|
94
|
+
updatedAt,
|
|
95
|
+
analysisResult,
|
|
96
|
+
serverData,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useTokenManagement } from './hooks/useTokenManagement';
|
|
3
|
+
import { useServerStatus } from './hooks/useServerStatus';
|
|
4
|
+
import { useMcpDiscovery } from './hooks/useMcpDiscovery';
|
|
5
|
+
import { useScanOperations } from './hooks/useScanOperations';
|
|
6
|
+
import { useScanList } from './hooks/useScanList';
|
|
7
|
+
import { useCacheManagement } from './hooks/useCacheManagement';
|
|
8
|
+
|
|
9
|
+
export function useSmartScan() {
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
|
|
12
|
+
const { apiToken, setApiToken, saveToken, saveTokenTimeoutRef } = useTokenManagement();
|
|
13
|
+
const { serverStatus } = useServerStatus();
|
|
14
|
+
const {
|
|
15
|
+
mcpData,
|
|
16
|
+
discoveredServers,
|
|
17
|
+
selectedServers,
|
|
18
|
+
setSelectedServers,
|
|
19
|
+
loadingData,
|
|
20
|
+
discoverMcpData,
|
|
21
|
+
makeMcpRequest,
|
|
22
|
+
} = useMcpDiscovery(setError);
|
|
23
|
+
const { scanning, scanResult, scanResults, runScan } = useScanOperations(
|
|
24
|
+
apiToken,
|
|
25
|
+
discoveredServers,
|
|
26
|
+
selectedServers,
|
|
27
|
+
setError
|
|
28
|
+
);
|
|
29
|
+
const {
|
|
30
|
+
allScans,
|
|
31
|
+
loadingScans,
|
|
32
|
+
loadAllScans,
|
|
33
|
+
selectedScan,
|
|
34
|
+
setSelectedScan,
|
|
35
|
+
loadingScanDetail,
|
|
36
|
+
loadScanDetail,
|
|
37
|
+
} = useScanList(apiToken, setError);
|
|
38
|
+
const { clearingCache, clearCache } = useCacheManagement(
|
|
39
|
+
discoveredServers,
|
|
40
|
+
discoverMcpData,
|
|
41
|
+
setError
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
apiToken,
|
|
46
|
+
setApiToken,
|
|
47
|
+
serverStatus,
|
|
48
|
+
mcpData,
|
|
49
|
+
discoveredServers,
|
|
50
|
+
selectedServers,
|
|
51
|
+
setSelectedServers,
|
|
52
|
+
loadingData,
|
|
53
|
+
scanning,
|
|
54
|
+
scanResult,
|
|
55
|
+
scanResults,
|
|
56
|
+
error,
|
|
57
|
+
saveToken,
|
|
58
|
+
discoverMcpData,
|
|
59
|
+
runScan,
|
|
60
|
+
clearCache,
|
|
61
|
+
clearingCache,
|
|
62
|
+
saveTokenTimeoutRef,
|
|
63
|
+
allScans,
|
|
64
|
+
loadingScans,
|
|
65
|
+
loadAllScans,
|
|
66
|
+
selectedScan,
|
|
67
|
+
setSelectedScan,
|
|
68
|
+
loadingScanDetail,
|
|
69
|
+
loadScanDetail,
|
|
70
|
+
makeMcpRequest,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { colors } from '../../theme';
|
|
2
|
+
|
|
3
|
+
export function getRiskLevelColor(riskLevel) {
|
|
4
|
+
if (!riskLevel) return colors.textTertiary;
|
|
5
|
+
switch (riskLevel.toLowerCase()) {
|
|
6
|
+
case 'none':
|
|
7
|
+
return colors.accentGreen;
|
|
8
|
+
case 'low':
|
|
9
|
+
return colors.accentBlue;
|
|
10
|
+
case 'medium':
|
|
11
|
+
return colors.accentOrange;
|
|
12
|
+
case 'high':
|
|
13
|
+
return colors.error;
|
|
14
|
+
case 'critical':
|
|
15
|
+
return colors.error;
|
|
16
|
+
default:
|
|
17
|
+
return colors.textTertiary;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { colors } from '../theme';
|
|
2
|
+
import {
|
|
3
|
+
IconShield,
|
|
4
|
+
IconExternalLink,
|
|
5
|
+
IconAlertTriangle,
|
|
6
|
+
IconCheck,
|
|
7
|
+
IconClock,
|
|
8
|
+
} from '@tabler/icons-react';
|
|
9
|
+
|
|
10
|
+
export const ShieldIcon = ({ size = 24, color = 'currentColor' }) => (
|
|
11
|
+
<IconShield size={size} stroke={1.5} color={color} />
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const ExternalLinkIcon = ({ size = 16, color = 'currentColor' }) => (
|
|
15
|
+
<IconExternalLink size={size} stroke={1.5} color={color} />
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const AlertIcon = ({ size = 16, color = 'currentColor' }) => (
|
|
19
|
+
<IconAlertTriangle size={size} stroke={1.5} color={color} />
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const CheckIcon = ({ size = 16, color = 'currentColor' }) => (
|
|
23
|
+
<IconCheck size={size} stroke={1.5} color={color} />
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const CacheIcon = ({ size = 16, color = 'currentColor' }) => (
|
|
27
|
+
<IconClock size={size} stroke={1.5} color={color} />
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const LoadingSpinner = ({ size = 16, color = colors.accentBlue }) => (
|
|
31
|
+
<div
|
|
32
|
+
style={{
|
|
33
|
+
width: size,
|
|
34
|
+
height: size,
|
|
35
|
+
border: `2px solid ${colors.borderLight}`,
|
|
36
|
+
borderTop: `2px solid ${color}`,
|
|
37
|
+
borderRadius: '50%',
|
|
38
|
+
animation: 'spin 0.8s linear infinite',
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export const EmptyStateIcon = () => (
|
|
44
|
+
<svg
|
|
45
|
+
width={64}
|
|
46
|
+
height={64}
|
|
47
|
+
viewBox="0 0 24 24"
|
|
48
|
+
fill="none"
|
|
49
|
+
stroke={colors.textTertiary}
|
|
50
|
+
strokeWidth="1.5"
|
|
51
|
+
strokeLinecap="round"
|
|
52
|
+
strokeLinejoin="round"
|
|
53
|
+
style={{ opacity: 0.5 }}
|
|
54
|
+
>
|
|
55
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
56
|
+
<path d="M9 12l2 2 4-4" />
|
|
57
|
+
</svg>
|
|
58
|
+
);
|