@mcp-shark/mcp-shark 1.4.1 → 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 +34 -42
- 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 +21 -40
- 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 +5 -7
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-read.js +5 -4
- package/mcp-server/lib/server/internal/handlers/tools-call.js +8 -10
- package/mcp-server/lib/server/internal/handlers/tools-list.js +6 -5
- package/mcp-server/lib/server/internal/run.js +5 -17
- package/mcp-server/lib/server/internal/server.js +14 -15
- package/mcp-server/lib/server/internal/session.js +8 -13
- 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 +91 -62
- 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 +140 -112
- 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 +87 -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 -10
- 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 +52 -23
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +13 -11
- 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 +66 -34
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +13 -11
- 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 +52 -23
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +13 -11
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +70 -0
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +90 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +118 -159
- package/ui/src/components/McpPlayground.jsx +105 -23
- 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 +37 -105
- 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 -244
|
@@ -8,36 +8,32 @@ export function useMcpDiscovery(setError) {
|
|
|
8
8
|
const [sessionId, setSessionId] = useState(null);
|
|
9
9
|
|
|
10
10
|
const makeMcpRequest = async (method, params = {}) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const response = await fetch('/api/playground/proxy', {
|
|
18
|
-
method: 'POST',
|
|
19
|
-
headers,
|
|
20
|
-
body: JSON.stringify({ method, params }),
|
|
21
|
-
});
|
|
11
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
12
|
+
if (sessionId) {
|
|
13
|
+
headers['Mcp-Session-Id'] = sessionId;
|
|
14
|
+
}
|
|
22
15
|
|
|
23
|
-
|
|
16
|
+
const response = await fetch('/api/playground/proxy', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers,
|
|
19
|
+
body: JSON.stringify({ method, params }),
|
|
20
|
+
});
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
response.headers.get('Mcp-Session-Id') ||
|
|
27
|
-
response.headers.get('mcp-session-id') ||
|
|
28
|
-
data._sessionId;
|
|
29
|
-
if (responseSessionId && responseSessionId !== sessionId) {
|
|
30
|
-
setSessionId(responseSessionId);
|
|
31
|
-
}
|
|
22
|
+
const data = await response.json();
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
const responseSessionId =
|
|
25
|
+
response.headers.get('Mcp-Session-Id') ||
|
|
26
|
+
response.headers.get('mcp-session-id') ||
|
|
27
|
+
data._sessionId;
|
|
28
|
+
if (responseSessionId && responseSessionId !== sessionId) {
|
|
29
|
+
setSessionId(responseSessionId);
|
|
30
|
+
}
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
throw err;
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(data.error?.message || data.message || 'Request failed');
|
|
40
34
|
}
|
|
35
|
+
|
|
36
|
+
return data.result || data;
|
|
41
37
|
};
|
|
42
38
|
|
|
43
39
|
const discoverMcpData = async () => {
|
|
@@ -95,7 +91,7 @@ export function useMcpDiscovery(setError) {
|
|
|
95
91
|
setMcpData({
|
|
96
92
|
server: {
|
|
97
93
|
name: firstServer.name,
|
|
98
|
-
description:
|
|
94
|
+
description: 'Discovered from MCP config',
|
|
99
95
|
},
|
|
100
96
|
tools: firstServer.tools || [],
|
|
101
97
|
resources: firstServer.resources || [],
|
|
@@ -23,7 +23,7 @@ export function useScanList(apiToken, setError) {
|
|
|
23
23
|
if (cacheResponse.ok) {
|
|
24
24
|
const scans = cacheData.scans || [];
|
|
25
25
|
console.log(`[useScanList] Loaded ${scans.length} cached scans from API`);
|
|
26
|
-
console.log(
|
|
26
|
+
console.log('[useScanList] Full cacheData:', cacheData);
|
|
27
27
|
|
|
28
28
|
// Debug: Log first scan structure to see what we're receiving
|
|
29
29
|
if (scans.length > 0) {
|
|
@@ -54,9 +54,9 @@ export function useScanList(apiToken, setError) {
|
|
|
54
54
|
// Extract server name from multiple possible locations
|
|
55
55
|
// Handle empty strings, null, undefined
|
|
56
56
|
const serverName =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
scan.serverName?.trim() ||
|
|
58
|
+
scan.server_name?.trim() ||
|
|
59
|
+
scan.server?.name?.trim() ||
|
|
60
60
|
'Unknown Server';
|
|
61
61
|
|
|
62
62
|
console.log(`[useScanList] Extracted serverName for scan ${index}: "${serverName}"`);
|
|
@@ -79,12 +79,13 @@ export function useScanList(apiToken, setError) {
|
|
|
79
79
|
// Get the actual scan data - it might be nested
|
|
80
80
|
// scan.data could be the scan result from API which has { success, data, scan_id, ... }
|
|
81
81
|
// or it could be the direct scan data
|
|
82
|
-
|
|
82
|
+
const baseScanData = scan.data || scan.result || scan;
|
|
83
83
|
|
|
84
84
|
// If scanData has a nested 'data' property (from API response), use that
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
const scanData =
|
|
86
|
+
baseScanData?.data && typeof baseScanData.data === 'object'
|
|
87
|
+
? baseScanData.data
|
|
88
|
+
: baseScanData;
|
|
88
89
|
|
|
89
90
|
const transformed = {
|
|
90
91
|
serverName: serverName,
|
|
@@ -144,7 +145,7 @@ export function useScanList(apiToken, setError) {
|
|
|
144
145
|
r.data?.data?.id === scanId ||
|
|
145
146
|
r.data?.data?.scan_id === scanId
|
|
146
147
|
);
|
|
147
|
-
if (cachedResult
|
|
148
|
+
if (cachedResult?.cached && cachedResult.data?.data) {
|
|
148
149
|
// Use the cached scan data directly
|
|
149
150
|
const scanData = cachedResult.data.data;
|
|
150
151
|
setSelectedScan({
|
|
@@ -43,21 +43,30 @@ export function useScanOperations(apiToken, discoveredServers, selectedServers,
|
|
|
43
43
|
const data = await response.json();
|
|
44
44
|
|
|
45
45
|
if (!response.ok) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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;
|
|
46
|
+
const formatValidationErrors = (details) => {
|
|
47
|
+
if (!Array.isArray(details)) {
|
|
48
|
+
return null;
|
|
59
49
|
}
|
|
60
|
-
|
|
50
|
+
return `Validation failed: ${details
|
|
51
|
+
.map((d) => {
|
|
52
|
+
if (typeof d === 'string') {
|
|
53
|
+
return d;
|
|
54
|
+
}
|
|
55
|
+
if (d.field && d.message) {
|
|
56
|
+
return `${d.field}: ${d.message}`;
|
|
57
|
+
}
|
|
58
|
+
return JSON.stringify(d);
|
|
59
|
+
})
|
|
60
|
+
.join('; ')}`;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const errorMessage =
|
|
64
|
+
response.status === 400 && data.details
|
|
65
|
+
? formatValidationErrors(data.details) ||
|
|
66
|
+
data.error ||
|
|
67
|
+
data.message ||
|
|
68
|
+
`API error: ${response.status}`
|
|
69
|
+
: data.error || data.message || `API error: ${response.status}`;
|
|
61
70
|
|
|
62
71
|
setError(errorMessage);
|
|
63
72
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export function useServerStatus() {
|
|
4
4
|
const [serverStatus, setServerStatus] = useState(null);
|
|
@@ -17,7 +17,7 @@ export function useServerStatus() {
|
|
|
17
17
|
}
|
|
18
18
|
const data = await res.json();
|
|
19
19
|
setServerStatus(data);
|
|
20
|
-
} catch (
|
|
20
|
+
} catch (_err) {
|
|
21
21
|
setServerStatus({ running: false });
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export function useTokenManagement() {
|
|
4
4
|
const [apiToken, setApiToken] = useState('');
|
|
@@ -22,7 +22,7 @@ export function useTokenManagement() {
|
|
|
22
22
|
setApiToken(data.token);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
} catch (
|
|
25
|
+
} catch (_err) {
|
|
26
26
|
console.debug('No stored token found');
|
|
27
27
|
}
|
|
28
28
|
};
|
|
@@ -4,15 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
export function getScanValue(scan, path) {
|
|
6
6
|
const paths = path.split('.');
|
|
7
|
-
|
|
8
|
-
for (const p of paths) {
|
|
7
|
+
return paths.reduce((value, p) => {
|
|
9
8
|
if (value && typeof value === 'object' && p in value) {
|
|
10
|
-
|
|
11
|
-
} else {
|
|
12
|
-
return null;
|
|
9
|
+
return value[p];
|
|
13
10
|
}
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
return null;
|
|
12
|
+
}, scan);
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
export function normalizeScanData(scan) {
|
|
@@ -24,8 +21,8 @@ export function normalizeScanData(scan) {
|
|
|
24
21
|
getScanValue(scan, 'scan_id') ||
|
|
25
22
|
getScanValue(scan, 'data.id') ||
|
|
26
23
|
getScanValue(scan, 'data.scan_id') ||
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
actualScan?.id ||
|
|
25
|
+
actualScan?.scan_id;
|
|
29
26
|
|
|
30
27
|
const serverName =
|
|
31
28
|
scan.serverName || // Check top-level first (for cached scans)
|
|
@@ -35,7 +32,7 @@ export function normalizeScanData(scan) {
|
|
|
35
32
|
getScanValue(scan, 'server.name') ||
|
|
36
33
|
getScanValue(scan, 'data.server.name') ||
|
|
37
34
|
getScanValue(scan, 'data.data.server.name') ||
|
|
38
|
-
|
|
35
|
+
scan.server?.name || // Check nested server object
|
|
39
36
|
'Unknown Server';
|
|
40
37
|
|
|
41
38
|
const status =
|
|
@@ -61,21 +58,29 @@ export function normalizeScanData(scan) {
|
|
|
61
58
|
getScanValue(scan, 'data.updated_at') ||
|
|
62
59
|
getScanValue(scan, 'data.data.updated_at');
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
const baseAnalysisResult =
|
|
65
62
|
getScanValue(scan, 'result.analysis_result') ||
|
|
66
63
|
getScanValue(scan, 'analysis_result') ||
|
|
67
64
|
getScanValue(scan, 'data.analysis_result') ||
|
|
68
65
|
getScanValue(scan, 'data.data.analysis_result') ||
|
|
69
66
|
getScanValue(scan, 'data.data.data.analysis_result');
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
68
|
+
const extractAnalysisResult = (base, actual) => {
|
|
69
|
+
if (base) {
|
|
70
|
+
return base;
|
|
74
71
|
}
|
|
75
|
-
if (
|
|
76
|
-
|
|
72
|
+
if (actual && typeof actual === 'object') {
|
|
73
|
+
if (actual.tool_findings || actual.prompt_findings || actual.resource_findings) {
|
|
74
|
+
return actual;
|
|
75
|
+
}
|
|
76
|
+
if (actual.analysis_result) {
|
|
77
|
+
return actual.analysis_result;
|
|
78
|
+
}
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const analysisResult = extractAnalysisResult(baseAnalysisResult, actualScan);
|
|
79
84
|
|
|
80
85
|
const serverData =
|
|
81
86
|
getScanValue(scan, 'result.mcp_server_data.server') ||
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { useServerStatus } from './hooks/useServerStatus';
|
|
2
|
+
import { useCacheManagement } from './hooks/useCacheManagement';
|
|
4
3
|
import { useMcpDiscovery } from './hooks/useMcpDiscovery';
|
|
5
|
-
import { useScanOperations } from './hooks/useScanOperations';
|
|
6
4
|
import { useScanList } from './hooks/useScanList';
|
|
7
|
-
import {
|
|
5
|
+
import { useScanOperations } from './hooks/useScanOperations';
|
|
6
|
+
import { useServerStatus } from './hooks/useServerStatus';
|
|
7
|
+
import { useTokenManagement } from './hooks/useTokenManagement';
|
|
8
8
|
|
|
9
9
|
export function useSmartScan() {
|
|
10
10
|
const [error, setError] = useState(null);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { colors } from '../../theme';
|
|
2
2
|
|
|
3
3
|
export function getRiskLevelColor(riskLevel) {
|
|
4
|
-
if (!riskLevel)
|
|
4
|
+
if (!riskLevel) {
|
|
5
|
+
return colors.textTertiary;
|
|
6
|
+
}
|
|
5
7
|
switch (riskLevel.toLowerCase()) {
|
|
6
8
|
case 'none':
|
|
7
9
|
return colors.accentGreen;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { colors } from '../theme';
|
|
2
1
|
import {
|
|
3
|
-
IconShield,
|
|
4
|
-
IconExternalLink,
|
|
5
2
|
IconAlertTriangle,
|
|
6
3
|
IconCheck,
|
|
7
4
|
IconClock,
|
|
5
|
+
IconExternalLink,
|
|
6
|
+
IconShield,
|
|
8
7
|
} from '@tabler/icons-react';
|
|
8
|
+
import { colors } from '../theme';
|
|
9
9
|
|
|
10
10
|
export const ShieldIcon = ({ size = 24, color = 'currentColor' }) => (
|
|
11
11
|
<IconShield size={size} stroke={1.5} color={color} />
|
|
@@ -51,7 +51,10 @@ export const EmptyStateIcon = () => (
|
|
|
51
51
|
strokeLinecap="round"
|
|
52
52
|
strokeLinejoin="round"
|
|
53
53
|
style={{ opacity: 0.5 }}
|
|
54
|
+
role="img"
|
|
55
|
+
aria-label="Empty state icon"
|
|
54
56
|
>
|
|
57
|
+
<title>Empty state icon</title>
|
|
55
58
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
56
59
|
<path d="M9 12l2 2 4-4" />
|
|
57
60
|
</svg>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { colors, fonts } from '../../theme';
|
|
3
1
|
import anime from 'animejs';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { colors, fonts } from '../../theme';
|
|
4
4
|
|
|
5
5
|
export default function DesktopTabs({ tabs, activeTab, onTabChange, tabRefs, indicatorRef }) {
|
|
6
6
|
useEffect(() => {
|
|
@@ -21,8 +21,13 @@ export default function DesktopTabs({ tabs, activeTab, onTabChange, tabRefs, ind
|
|
|
21
21
|
<div style={{ position: 'relative', display: 'flex', flex: 1 }}>
|
|
22
22
|
{tabs.map((tab) => (
|
|
23
23
|
<button
|
|
24
|
+
type="button"
|
|
24
25
|
key={tab.id}
|
|
25
|
-
ref={(el) =>
|
|
26
|
+
ref={(el) => {
|
|
27
|
+
if (el) {
|
|
28
|
+
tabRefs.current[tab.id] = el;
|
|
29
|
+
}
|
|
30
|
+
}}
|
|
26
31
|
data-tour={
|
|
27
32
|
tab.id === 'traffic'
|
|
28
33
|
? 'traffic-tab'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
-
import {
|
|
2
|
+
import { ChevronDownIcon, MenuIcon } from '../TabNavigationIcons';
|
|
3
3
|
|
|
4
4
|
export default function MobileDropdown({
|
|
5
5
|
tabs,
|
|
@@ -15,6 +15,7 @@ export default function MobileDropdown({
|
|
|
15
15
|
ref={dropdownRef}
|
|
16
16
|
>
|
|
17
17
|
<button
|
|
18
|
+
type="button"
|
|
18
19
|
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
|
19
20
|
style={{
|
|
20
21
|
display: 'flex',
|
|
@@ -74,6 +75,7 @@ export default function MobileDropdown({
|
|
|
74
75
|
const Icon = tab.icon;
|
|
75
76
|
return (
|
|
76
77
|
<button
|
|
78
|
+
type="button"
|
|
77
79
|
key={tab.id}
|
|
78
80
|
onClick={() => {
|
|
79
81
|
onTabChange(tab.id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import anime from 'animejs';
|
|
1
2
|
import { useEffect, useRef } from 'react';
|
|
2
3
|
import { colors, fonts } from '../theme';
|
|
3
|
-
import anime from 'animejs';
|
|
4
4
|
|
|
5
5
|
function TabNavigation({ tabs, activeTab, onTabChange }) {
|
|
6
6
|
const tabRefs = useRef({});
|
|
@@ -18,7 +18,7 @@ function TabNavigation({ tabs, activeTab, onTabChange }) {
|
|
|
18
18
|
easing: 'easeOutExpo',
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
}, [activeTab
|
|
21
|
+
}, [activeTab]);
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<div
|
|
@@ -35,7 +35,12 @@ function TabNavigation({ tabs, activeTab, onTabChange }) {
|
|
|
35
35
|
{tabs.map((tab) => (
|
|
36
36
|
<button
|
|
37
37
|
key={tab}
|
|
38
|
-
|
|
38
|
+
type="button"
|
|
39
|
+
ref={(el) => {
|
|
40
|
+
if (el) {
|
|
41
|
+
tabRefs.current[tab] = el;
|
|
42
|
+
}
|
|
43
|
+
}}
|
|
39
44
|
onClick={() => onTabChange(tab)}
|
|
40
45
|
style={{
|
|
41
46
|
padding: '10px 18px',
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Tabler Icons for Tab Navigation
|
|
2
2
|
// Using @tabler/icons-react - install with: npm install @tabler/icons-react
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
IconBrandStackoverflow,
|
|
5
|
+
IconChevronDown,
|
|
5
6
|
IconFileText,
|
|
7
|
+
IconMenu2,
|
|
8
|
+
IconNetwork,
|
|
6
9
|
IconSettings,
|
|
7
|
-
IconBrandStackoverflow,
|
|
8
10
|
IconShield,
|
|
9
|
-
IconMenu2,
|
|
10
|
-
IconChevronDown,
|
|
11
11
|
} from '@tabler/icons-react';
|
|
12
12
|
|
|
13
13
|
// Wrapper components to match existing API
|
|
@@ -19,6 +19,7 @@ export default function TourTooltipButtons({
|
|
|
19
19
|
}}
|
|
20
20
|
>
|
|
21
21
|
<button
|
|
22
|
+
type="button"
|
|
22
23
|
onClick={(e) => {
|
|
23
24
|
e.stopPropagation();
|
|
24
25
|
onSkip();
|
|
@@ -52,6 +53,7 @@ export default function TourTooltipButtons({
|
|
|
52
53
|
<div style={{ display: 'flex', gap: '8px', pointerEvents: 'auto' }}>
|
|
53
54
|
{currentStep > 0 && (
|
|
54
55
|
<button
|
|
56
|
+
type="button"
|
|
55
57
|
onClick={(e) => {
|
|
56
58
|
e.stopPropagation();
|
|
57
59
|
onPrevious();
|
|
@@ -82,6 +84,7 @@ export default function TourTooltipButtons({
|
|
|
82
84
|
</button>
|
|
83
85
|
)}
|
|
84
86
|
<button
|
|
87
|
+
type="button"
|
|
85
88
|
onClick={(e) => {
|
|
86
89
|
e.stopPropagation();
|
|
87
90
|
onNext();
|
|
@@ -8,7 +8,10 @@ export const CloseIcon = ({ size = 16, color = 'currentColor' }) => (
|
|
|
8
8
|
strokeWidth="2"
|
|
9
9
|
strokeLinecap="round"
|
|
10
10
|
strokeLinejoin="round"
|
|
11
|
+
role="img"
|
|
12
|
+
aria-label="Close icon"
|
|
11
13
|
>
|
|
14
|
+
<title>Close icon</title>
|
|
12
15
|
<line x1="18" y1="6" x2="6" y2="18" />
|
|
13
16
|
<line x1="6" y1="6" x2="18" y2="18" />
|
|
14
17
|
</svg>
|
|
@@ -24,7 +27,10 @@ export const ChevronRight = ({ size = 16, color = 'currentColor' }) => (
|
|
|
24
27
|
strokeWidth="2"
|
|
25
28
|
strokeLinecap="round"
|
|
26
29
|
strokeLinejoin="round"
|
|
30
|
+
role="img"
|
|
31
|
+
aria-label="Chevron right icon"
|
|
27
32
|
>
|
|
33
|
+
<title>Chevron right icon</title>
|
|
28
34
|
<polyline points="9 18 15 12 9 6" />
|
|
29
35
|
</svg>
|
|
30
36
|
);
|
|
@@ -39,7 +45,10 @@ export const ChevronLeft = ({ size = 16, color = 'currentColor' }) => (
|
|
|
39
45
|
strokeWidth="2"
|
|
40
46
|
strokeLinecap="round"
|
|
41
47
|
strokeLinejoin="round"
|
|
48
|
+
role="img"
|
|
49
|
+
aria-label="Chevron left icon"
|
|
42
50
|
>
|
|
51
|
+
<title>Chevron left icon</title>
|
|
43
52
|
<polyline points="15 18 9 12 15 6" />
|
|
44
53
|
</svg>
|
|
45
54
|
);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
export function useTooltipPosition(elementRect, step,
|
|
3
|
+
export function useTooltipPosition(elementRect, step, _currentStep) {
|
|
4
4
|
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
|
|
5
5
|
const [isDragging, setIsDragging] = useState(false);
|
|
6
6
|
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
|
7
7
|
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
setTooltipPosition({ x: 0, y: 0 });
|
|
10
|
-
}, [
|
|
10
|
+
}, []);
|
|
11
11
|
|
|
12
12
|
const handleMouseDown = (e, tooltipRef) => {
|
|
13
13
|
e.preventDefault();
|
|
@@ -23,7 +23,9 @@ export function useTooltipPosition(elementRect, step, currentStep) {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
useEffect(() => {
|
|
26
|
-
if (!isDragging)
|
|
26
|
+
if (!isDragging) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
const handleMouseMove = (e) => {
|
|
29
31
|
setTooltipPosition({
|
|
@@ -46,7 +48,9 @@ export function useTooltipPosition(elementRect, step, currentStep) {
|
|
|
46
48
|
}, [isDragging, dragOffset]);
|
|
47
49
|
|
|
48
50
|
const calculatePosition = () => {
|
|
49
|
-
if (!elementRect)
|
|
51
|
+
if (!elementRect) {
|
|
52
|
+
return { left: 0, top: 0, transform: 'none' };
|
|
53
|
+
}
|
|
50
54
|
|
|
51
55
|
const tooltipWidth = 350;
|
|
52
56
|
const tooltipHeight = 200;
|
|
@@ -62,42 +66,65 @@ export function useTooltipPosition(elementRect, step, currentStep) {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
const position = step.position || 'bottom';
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
left = elementRect.right + spacing;
|
|
76
|
-
top = elementRect.top + elementRect.height / 2;
|
|
77
|
-
transform = 'translateY(-50%)';
|
|
78
|
-
if (left + tooltipWidth > window.innerWidth - 10) {
|
|
79
|
-
left = elementRect.left - tooltipWidth - spacing;
|
|
69
|
+
|
|
70
|
+
const calculatePosition = (position, elementRect, tooltipWidth, tooltipHeight, spacing) => {
|
|
71
|
+
if (position === 'left') {
|
|
72
|
+
const baseLeft = elementRect.left - tooltipWidth - spacing;
|
|
73
|
+
const left = baseLeft < 10 ? elementRect.right + spacing : baseLeft;
|
|
74
|
+
return {
|
|
75
|
+
left,
|
|
76
|
+
top: elementRect.top + elementRect.height / 2,
|
|
77
|
+
transform: 'translateY(-50%)',
|
|
78
|
+
};
|
|
80
79
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
|
|
81
|
+
if (position === 'right') {
|
|
82
|
+
const baseLeft = elementRect.right + spacing;
|
|
83
|
+
const left =
|
|
84
|
+
baseLeft + tooltipWidth > window.innerWidth - 10
|
|
85
|
+
? elementRect.left - tooltipWidth - spacing
|
|
86
|
+
: baseLeft;
|
|
87
|
+
return {
|
|
88
|
+
left,
|
|
89
|
+
top: elementRect.top + elementRect.height / 2,
|
|
90
|
+
transform: 'translateY(-50%)',
|
|
91
|
+
};
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
|
|
94
|
+
if (position === 'top') {
|
|
95
|
+
const baseTop = elementRect.top - tooltipHeight - spacing;
|
|
96
|
+
const top = baseTop < 10 ? elementRect.bottom + spacing : baseTop;
|
|
97
|
+
return {
|
|
98
|
+
left: elementRect.left + elementRect.width / 2,
|
|
99
|
+
top,
|
|
100
|
+
transform: 'translate(-50%, 0)',
|
|
101
|
+
};
|
|
94
102
|
}
|
|
95
|
-
}
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
// bottom (default)
|
|
105
|
+
const baseTop = elementRect.bottom + spacing;
|
|
106
|
+
const top =
|
|
107
|
+
baseTop + tooltipHeight > window.innerHeight - 10
|
|
108
|
+
? elementRect.top - tooltipHeight - spacing
|
|
109
|
+
: baseTop;
|
|
110
|
+
return {
|
|
111
|
+
left: elementRect.left + elementRect.width / 2,
|
|
112
|
+
top,
|
|
113
|
+
transform: 'translate(-50%, 0)',
|
|
114
|
+
};
|
|
115
|
+
};
|
|
99
116
|
|
|
100
|
-
|
|
117
|
+
const rawPosition = calculatePosition(
|
|
118
|
+
position,
|
|
119
|
+
elementRect,
|
|
120
|
+
tooltipWidth,
|
|
121
|
+
tooltipHeight,
|
|
122
|
+
spacing
|
|
123
|
+
);
|
|
124
|
+
const left = Math.max(10, Math.min(rawPosition.left, window.innerWidth - tooltipWidth - 10));
|
|
125
|
+
const top = Math.max(10, Math.min(rawPosition.top, window.innerHeight - tooltipHeight - 10));
|
|
126
|
+
|
|
127
|
+
return { left, top, transform: rawPosition.transform };
|
|
101
128
|
};
|
|
102
129
|
|
|
103
130
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
2
|
import { colors, fonts } from '../theme';
|
|
3
|
-
import TourTooltipHeader from './TourTooltip/TourTooltipHeader';
|
|
4
3
|
import TourTooltipButtons from './TourTooltip/TourTooltipButtons';
|
|
4
|
+
import TourTooltipHeader from './TourTooltip/TourTooltipHeader';
|
|
5
5
|
import { useTooltipPosition } from './TourTooltip/useTooltipPosition';
|
|
6
6
|
|
|
7
7
|
function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPrevious, onSkip }) {
|
|
@@ -17,9 +17,17 @@ function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPre
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
21
22
|
ref={tooltipRef}
|
|
22
23
|
onMouseDown={(e) => handleMouseDown(e, tooltipRef)}
|
|
24
|
+
onKeyDown={(e) => {
|
|
25
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
handleMouseDown(e, tooltipRef);
|
|
28
|
+
}
|
|
29
|
+
}}
|
|
30
|
+
aria-label="Draggable tooltip"
|
|
23
31
|
style={{
|
|
24
32
|
position: 'fixed',
|
|
25
33
|
left: `${position.left}px`,
|
|
@@ -76,7 +84,7 @@ function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPre
|
|
|
76
84
|
onPrevious={onPrevious}
|
|
77
85
|
onSkip={onSkip}
|
|
78
86
|
/>
|
|
79
|
-
</
|
|
87
|
+
</button>
|
|
80
88
|
);
|
|
81
89
|
}
|
|
82
90
|
|