@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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { IconChevronRight, IconExternalLink, IconEye } from '@tabler/icons-react';
|
|
1
2
|
import { useState } from 'react';
|
|
2
3
|
import { colors, fonts } from '../../../theme';
|
|
3
|
-
import { IconChevronRight, IconEye, IconExternalLink } from '@tabler/icons-react';
|
|
4
4
|
import { getRiskLevelColor } from '../utils';
|
|
5
5
|
|
|
6
6
|
export default function ScanListItem({ scan, onSelectScan }) {
|
|
@@ -25,6 +25,13 @@ export default function ScanListItem({ scan, onSelectScan }) {
|
|
|
25
25
|
cursor: 'pointer',
|
|
26
26
|
}}
|
|
27
27
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
28
|
+
onKeyDown={(e) => {
|
|
29
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
setIsExpanded(!isExpanded);
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
34
|
+
aria-label={`Toggle scan ${scan.scanId || 'details'}`}
|
|
28
35
|
>
|
|
29
36
|
<div
|
|
30
37
|
style={{
|
|
@@ -101,8 +108,15 @@ export default function ScanListItem({ scan, onSelectScan }) {
|
|
|
101
108
|
flexShrink: 0,
|
|
102
109
|
}}
|
|
103
110
|
onClick={(e) => e.stopPropagation()}
|
|
111
|
+
onKeyDown={(e) => {
|
|
112
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
104
117
|
>
|
|
105
118
|
<button
|
|
119
|
+
type="button"
|
|
106
120
|
onClick={() => {
|
|
107
121
|
onSelectScan(scan.id);
|
|
108
122
|
}}
|
|
@@ -3,7 +3,9 @@ import { getRiskLevelColor } from './utils';
|
|
|
3
3
|
|
|
4
4
|
export default function ScanOverviewSection({ status, overallRiskLevel, createdAt, updatedAt }) {
|
|
5
5
|
const formatDate = (dateString) => {
|
|
6
|
-
if (!dateString)
|
|
6
|
+
if (!dateString) {
|
|
7
|
+
return 'N/A';
|
|
8
|
+
}
|
|
7
9
|
return new Date(dateString).toLocaleString();
|
|
8
10
|
};
|
|
9
11
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { colors } from '../../theme';
|
|
2
|
+
import BatchResultsDisplay from './BatchResultsDisplay';
|
|
2
3
|
import EmptyState from './EmptyState';
|
|
3
4
|
import ErrorDisplay from './ErrorDisplay';
|
|
4
5
|
import ScanningProgress from './ScanningProgress';
|
|
5
|
-
import BatchResultsDisplay from './BatchResultsDisplay';
|
|
6
6
|
import SingleResultDisplay from './SingleResultDisplay';
|
|
7
7
|
|
|
8
8
|
export default function ScanResultsDisplay({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { colors } from '../../theme';
|
|
2
|
-
import ServerSelectionRow from './ServerSelectionRow';
|
|
3
|
-
import ScanResultsDisplay from './ScanResultsDisplay';
|
|
4
2
|
import ScanDetailView from './ScanDetailView';
|
|
3
|
+
import ScanResultsDisplay from './ScanResultsDisplay';
|
|
4
|
+
import ServerSelectionRow from './ServerSelectionRow';
|
|
5
5
|
|
|
6
6
|
export default function ScanViewContent({
|
|
7
7
|
discoveredServers,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { LoadingSpinner } from '../SmartScanIcons';
|
|
2
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
+
import { LoadingSpinner } from '../SmartScanIcons';
|
|
3
3
|
|
|
4
4
|
export default function ScanningProgress({ scanning, selectedServers }) {
|
|
5
|
-
if (!scanning)
|
|
5
|
+
if (!scanning) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
return (
|
|
8
10
|
<div
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CheckIcon, ShieldIcon, LoadingSpinner } from '../SmartScanIcons';
|
|
2
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
+
import { LoadingSpinner, ShieldIcon } from '../SmartScanIcons';
|
|
3
3
|
|
|
4
4
|
export default function ServerSelectionRow({
|
|
5
5
|
discoveredServers,
|
|
@@ -57,7 +57,7 @@ export default function ServerSelectionRow({
|
|
|
57
57
|
const isSelected = selectedServers.has(server.name);
|
|
58
58
|
return (
|
|
59
59
|
<label
|
|
60
|
-
key={idx}
|
|
60
|
+
key={server.name || `server-${idx}`}
|
|
61
61
|
style={{
|
|
62
62
|
display: 'flex',
|
|
63
63
|
alignItems: 'center',
|
|
@@ -119,6 +119,7 @@ export default function ServerSelectionRow({
|
|
|
119
119
|
})}
|
|
120
120
|
</div>
|
|
121
121
|
<button
|
|
122
|
+
type="button"
|
|
122
123
|
onClick={toggleSelectAll}
|
|
123
124
|
style={{
|
|
124
125
|
padding: '6px 12px',
|
|
@@ -143,6 +144,7 @@ export default function ServerSelectionRow({
|
|
|
143
144
|
{selectedServers.size === discoveredServers.length ? 'Deselect All' : 'Select All'}
|
|
144
145
|
</button>
|
|
145
146
|
<button
|
|
147
|
+
type="button"
|
|
146
148
|
onClick={runScan}
|
|
147
149
|
disabled={!apiToken || selectedServers.size === 0 || scanning}
|
|
148
150
|
style={{
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { ExternalLinkIcon } from '../SmartScanIcons';
|
|
2
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
+
import { ExternalLinkIcon } from '../SmartScanIcons';
|
|
3
3
|
import { getRiskLevelColor } from './utils';
|
|
4
4
|
|
|
5
5
|
export default function SingleResultDisplay({ scanResult }) {
|
|
6
|
-
if (!scanResult)
|
|
6
|
+
if (!scanResult) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
7
9
|
|
|
8
10
|
return (
|
|
9
11
|
<div
|
|
@@ -218,7 +220,7 @@ export default function SingleResultDisplay({ scanResult }) {
|
|
|
218
220
|
}}
|
|
219
221
|
>
|
|
220
222
|
{scanResult.data.recommendations.map((rec, idx) => (
|
|
221
|
-
<li key={idx}>{rec}</li>
|
|
223
|
+
<li key={`recommendation-${idx}-${rec.substring(0, 30)}`}>{rec}</li>
|
|
222
224
|
))}
|
|
223
225
|
</ul>
|
|
224
226
|
</div>
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
2
|
import { colors, fonts } from '../../theme';
|
|
3
|
-
import { CheckIcon, LoadingSpinner, CacheIcon } from '../SmartScanIcons';
|
|
4
|
-
import { ExternalLinkIcon } from '../SmartScanIcons';
|
|
5
|
-
import { IconTrash } from '@tabler/icons-react';
|
|
6
3
|
import ConfirmationModal from '../ConfirmationModal';
|
|
4
|
+
import { CheckIcon, ExternalLinkIcon, LoadingSpinner } from '../SmartScanIcons';
|
|
7
5
|
|
|
8
6
|
export default function SmartScanControls({
|
|
9
7
|
apiToken,
|
|
@@ -12,13 +10,14 @@ export default function SmartScanControls({
|
|
|
12
10
|
loadingData,
|
|
13
11
|
discoverMcpData,
|
|
14
12
|
discoveredServers,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
runScan,
|
|
13
|
+
_selectedServers,
|
|
14
|
+
_setSelectedServers,
|
|
18
15
|
scanning,
|
|
19
16
|
clearCache,
|
|
20
17
|
clearingCache,
|
|
18
|
+
...rest
|
|
21
19
|
}) {
|
|
20
|
+
const { runScan: _runScan } = rest;
|
|
22
21
|
const saveTokenTimeoutRef = useRef(null);
|
|
23
22
|
const [showClearCacheModal, setShowClearCacheModal] = useState(false);
|
|
24
23
|
|
|
@@ -56,6 +55,7 @@ export default function SmartScanControls({
|
|
|
56
55
|
}}
|
|
57
56
|
>
|
|
58
57
|
<label
|
|
58
|
+
htmlFor="api-token-input"
|
|
59
59
|
style={{
|
|
60
60
|
fontSize: '12px',
|
|
61
61
|
fontWeight: '600',
|
|
@@ -68,6 +68,7 @@ export default function SmartScanControls({
|
|
|
68
68
|
</label>
|
|
69
69
|
<div style={{ position: 'relative', width: '200px' }}>
|
|
70
70
|
<input
|
|
71
|
+
id="api-token-input"
|
|
71
72
|
type="password"
|
|
72
73
|
value={apiToken}
|
|
73
74
|
onChange={(e) => handleTokenChange(e.target.value)}
|
|
@@ -145,6 +146,7 @@ export default function SmartScanControls({
|
|
|
145
146
|
}}
|
|
146
147
|
>
|
|
147
148
|
<label
|
|
149
|
+
htmlFor="servers-label"
|
|
148
150
|
style={{
|
|
149
151
|
fontSize: '12px',
|
|
150
152
|
fontWeight: '600',
|
|
@@ -156,6 +158,7 @@ export default function SmartScanControls({
|
|
|
156
158
|
Servers:
|
|
157
159
|
</label>
|
|
158
160
|
<button
|
|
161
|
+
type="button"
|
|
159
162
|
onClick={discoverMcpData}
|
|
160
163
|
disabled={loadingData}
|
|
161
164
|
style={{
|
|
@@ -224,6 +227,7 @@ export default function SmartScanControls({
|
|
|
224
227
|
|
|
225
228
|
{/* Clear Cache Button */}
|
|
226
229
|
<button
|
|
230
|
+
type="button"
|
|
227
231
|
onClick={() => setShowClearCacheModal(true)}
|
|
228
232
|
disabled={clearingCache}
|
|
229
233
|
style={{
|
|
@@ -13,6 +13,7 @@ export default function ViewModeTabs({ viewMode, setViewMode, onSwitchToScan, on
|
|
|
13
13
|
}}
|
|
14
14
|
>
|
|
15
15
|
<button
|
|
16
|
+
type="button"
|
|
16
17
|
onClick={() => {
|
|
17
18
|
setViewMode('scan');
|
|
18
19
|
onSwitchToScan?.();
|
|
@@ -33,6 +34,7 @@ export default function ViewModeTabs({ viewMode, setViewMode, onSwitchToScan, on
|
|
|
33
34
|
Scan Servers
|
|
34
35
|
</button>
|
|
35
36
|
<button
|
|
37
|
+
type="button"
|
|
36
38
|
onClick={() => {
|
|
37
39
|
setViewMode('list');
|
|
38
40
|
onSwitchToList?.();
|
|
@@ -16,9 +16,8 @@ export function useCacheManagement(discoveredServers, discoverMcpData, setError)
|
|
|
16
16
|
await discoverMcpData();
|
|
17
17
|
}
|
|
18
18
|
return { success: true, message: data.message };
|
|
19
|
-
} else {
|
|
20
|
-
throw new Error(data.error || 'Failed to clear cache');
|
|
21
19
|
}
|
|
20
|
+
throw new Error(data.error || 'Failed to clear cache');
|
|
22
21
|
} catch (err) {
|
|
23
22
|
setError(err.message || 'Failed to clear cache');
|
|
24
23
|
return { success: false, error: err.message };
|
|
@@ -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',
|