@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
|
@@ -110,7 +110,7 @@ export default function FindingsTable({ findings, type }) {
|
|
|
110
110
|
<tbody>
|
|
111
111
|
{findings.map((finding, index) => (
|
|
112
112
|
<tr
|
|
113
|
-
key={index}
|
|
113
|
+
key={`finding-${finding.id || finding.name || index}-${index}`}
|
|
114
114
|
style={{
|
|
115
115
|
borderBottom: `1px solid ${colors.borderLight}`,
|
|
116
116
|
transition: 'background 0.15s',
|
|
@@ -164,7 +164,7 @@ export default function FindingsTable({ findings, type }) {
|
|
|
164
164
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '2px' }}>
|
|
165
165
|
{finding.risk_tags?.map((tag, tagIndex) => (
|
|
166
166
|
<span
|
|
167
|
-
key={tagIndex}
|
|
167
|
+
key={`tag-${String(tag)}-${tagIndex}`}
|
|
168
168
|
style={{
|
|
169
169
|
padding: '2px 6px',
|
|
170
170
|
background: colors.bgCard,
|
|
@@ -192,7 +192,10 @@ export default function FindingsTable({ findings, type }) {
|
|
|
192
192
|
style={{ listStyle: 'disc', listStylePosition: 'inside', margin: 0, padding: 0 }}
|
|
193
193
|
>
|
|
194
194
|
{finding.reasons?.map((reason, reasonIndex) => (
|
|
195
|
-
<li
|
|
195
|
+
<li
|
|
196
|
+
key={`reason-${reasonIndex}-${reason.substring(0, 20)}`}
|
|
197
|
+
style={{ fontSize: '10px', marginBottom: '2px' }}
|
|
198
|
+
>
|
|
196
199
|
{reason}
|
|
197
200
|
</li>
|
|
198
201
|
))}
|
|
@@ -209,45 +212,46 @@ export default function FindingsTable({ findings, type }) {
|
|
|
209
212
|
>
|
|
210
213
|
{finding.safe_use_notes}
|
|
211
214
|
</td>
|
|
212
|
-
{type === 'tool' &&
|
|
213
|
-
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
215
|
+
{type === 'tool' &&
|
|
216
|
+
Object.prototype.hasOwnProperty.call(finding, 'is_potentially_poisoned') && (
|
|
217
|
+
<td style={{ padding: '8px' }}>
|
|
218
|
+
{finding.is_potentially_poisoned ? (
|
|
219
|
+
<span
|
|
220
|
+
style={{
|
|
221
|
+
display: 'inline-flex',
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
padding: '2px 6px',
|
|
224
|
+
fontSize: '10px',
|
|
225
|
+
fontWeight: '500',
|
|
226
|
+
borderRadius: '4px',
|
|
227
|
+
background: `${colors.error}20`,
|
|
228
|
+
color: colors.error,
|
|
229
|
+
border: `1px solid ${colors.error}40`,
|
|
230
|
+
fontFamily: fonts.body,
|
|
231
|
+
}}
|
|
232
|
+
>
|
|
233
|
+
Yes
|
|
234
|
+
</span>
|
|
235
|
+
) : (
|
|
236
|
+
<span
|
|
237
|
+
style={{
|
|
238
|
+
display: 'inline-flex',
|
|
239
|
+
alignItems: 'center',
|
|
240
|
+
padding: '2px 6px',
|
|
241
|
+
fontSize: '10px',
|
|
242
|
+
fontWeight: '500',
|
|
243
|
+
borderRadius: '4px',
|
|
244
|
+
background: `${colors.accentGreen}20`,
|
|
245
|
+
color: colors.accentGreen,
|
|
246
|
+
border: `1px solid ${colors.accentGreen}40`,
|
|
247
|
+
fontFamily: fonts.body,
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
No
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
</td>
|
|
254
|
+
)}
|
|
251
255
|
</tr>
|
|
252
256
|
))}
|
|
253
257
|
</tbody>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
-
import ScanResultsDisplay from './ScanResultsDisplay';
|
|
3
2
|
import ScanDetailView from './ScanDetailView';
|
|
3
|
+
import ScanResultsDisplay from './ScanResultsDisplay';
|
|
4
4
|
|
|
5
5
|
export default function ListViewContent({
|
|
6
6
|
error,
|
|
@@ -50,7 +50,7 @@ export default function ListViewContent({
|
|
|
50
50
|
<div
|
|
51
51
|
style={{
|
|
52
52
|
padding: '12px 16px',
|
|
53
|
-
background: colors.error
|
|
53
|
+
background: `${colors.error}20`,
|
|
54
54
|
border: `1px solid ${colors.error}`,
|
|
55
55
|
borderRadius: '8px',
|
|
56
56
|
marginBottom: '16px',
|
|
@@ -2,31 +2,33 @@ import { colors, fonts } from '../../theme';
|
|
|
2
2
|
import ExpandableSection from './ExpandableSection';
|
|
3
3
|
|
|
4
4
|
export default function NotablePatternsSection({ patterns }) {
|
|
5
|
-
if (!patterns || patterns.length === 0)
|
|
5
|
+
if (!patterns || patterns.length === 0) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
return (
|
|
8
10
|
<ExpandableSection title="Notable Patterns" count={patterns.length} defaultExpanded={false}>
|
|
9
11
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
10
12
|
{patterns.map((pattern, index) => (
|
|
11
13
|
<div
|
|
12
|
-
key={index}
|
|
14
|
+
key={`pattern-${pattern.type || index}-${index}`}
|
|
13
15
|
style={{
|
|
14
16
|
display: 'flex',
|
|
15
17
|
alignItems: 'flex-start',
|
|
16
18
|
gap: '8px',
|
|
17
19
|
padding: '8px',
|
|
18
|
-
background: colors.accentOrange
|
|
20
|
+
background: `${colors.accentOrange}10`,
|
|
19
21
|
borderRadius: '6px',
|
|
20
22
|
border: `1px solid ${colors.accentOrange}20`,
|
|
21
23
|
transition: 'all 0.15s',
|
|
22
24
|
}}
|
|
23
25
|
onMouseEnter={(e) => {
|
|
24
|
-
e.currentTarget.style.background = colors.accentOrange
|
|
25
|
-
e.currentTarget.style.borderColor = colors.accentOrange
|
|
26
|
+
e.currentTarget.style.background = `${colors.accentOrange}15`;
|
|
27
|
+
e.currentTarget.style.borderColor = `${colors.accentOrange}40`;
|
|
26
28
|
}}
|
|
27
29
|
onMouseLeave={(e) => {
|
|
28
|
-
e.currentTarget.style.background = colors.accentOrange
|
|
29
|
-
e.currentTarget.style.borderColor = colors.accentOrange
|
|
30
|
+
e.currentTarget.style.background = `${colors.accentOrange}10`;
|
|
31
|
+
e.currentTarget.style.borderColor = `${colors.accentOrange}20`;
|
|
30
32
|
}}
|
|
31
33
|
>
|
|
32
34
|
<div
|
|
@@ -36,7 +38,7 @@ export default function NotablePatternsSection({ patterns }) {
|
|
|
36
38
|
width: '16px',
|
|
37
39
|
height: '16px',
|
|
38
40
|
borderRadius: '50%',
|
|
39
|
-
background: colors.accentOrange
|
|
41
|
+
background: `${colors.accentOrange}30`,
|
|
40
42
|
border: `1px solid ${colors.accentOrange}60`,
|
|
41
43
|
display: 'flex',
|
|
42
44
|
alignItems: 'center',
|
|
@@ -47,7 +49,10 @@ export default function NotablePatternsSection({ patterns }) {
|
|
|
47
49
|
style={{ width: '10px', height: '10px', color: colors.accentOrange }}
|
|
48
50
|
fill="currentColor"
|
|
49
51
|
viewBox="0 0 20 20"
|
|
52
|
+
role="img"
|
|
53
|
+
aria-label="Pattern icon"
|
|
50
54
|
>
|
|
55
|
+
<title>Pattern icon</title>
|
|
51
56
|
<path
|
|
52
57
|
fillRule="evenodd"
|
|
53
58
|
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
-
import { getRiskLevelColor } from './utils';
|
|
3
2
|
import ExpandableSection from './ExpandableSection';
|
|
3
|
+
import { getRiskLevelColor } from './utils';
|
|
4
|
+
|
|
5
|
+
function renderReasonContent(overallReason) {
|
|
6
|
+
const separator = overallReason.includes('\n')
|
|
7
|
+
? '\n'
|
|
8
|
+
: overallReason.includes(' | ')
|
|
9
|
+
? ' | '
|
|
10
|
+
: null;
|
|
11
|
+
|
|
12
|
+
if (separator) {
|
|
13
|
+
return (
|
|
14
|
+
<ul
|
|
15
|
+
style={{
|
|
16
|
+
listStyle: 'disc',
|
|
17
|
+
listStylePosition: 'inside',
|
|
18
|
+
margin: 0,
|
|
19
|
+
paddingLeft: '8px',
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
{overallReason.split(separator).map((item, index) => (
|
|
23
|
+
<li
|
|
24
|
+
key={`reason-${index}-${item.trim().substring(0, 20)}`}
|
|
25
|
+
style={{ fontSize: '12px', marginBottom: '2px' }}
|
|
26
|
+
>
|
|
27
|
+
{item.trim()}
|
|
28
|
+
</li>
|
|
29
|
+
))}
|
|
30
|
+
</ul>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return <p style={{ fontSize: '12px' }}>{overallReason}</p>;
|
|
34
|
+
}
|
|
4
35
|
|
|
5
36
|
export default function OverallSummarySection({ overallRiskLevel, overallReason }) {
|
|
6
|
-
if (!overallRiskLevel)
|
|
37
|
+
if (!overallRiskLevel) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
7
40
|
|
|
8
41
|
return (
|
|
9
42
|
<ExpandableSection title="Overall Summary" count={overallReason ? 1 : 0} defaultExpanded={true}>
|
|
@@ -37,33 +70,7 @@ export default function OverallSummarySection({ overallRiskLevel, overallReason
|
|
|
37
70
|
</div>
|
|
38
71
|
{overallReason && (
|
|
39
72
|
<div style={{ fontSize: '12px', color: colors.textSecondary, fontFamily: fonts.body }}>
|
|
40
|
-
{(
|
|
41
|
-
const separator = overallReason.includes('\n')
|
|
42
|
-
? '\n'
|
|
43
|
-
: overallReason.includes(' | ')
|
|
44
|
-
? ' | '
|
|
45
|
-
: null;
|
|
46
|
-
|
|
47
|
-
if (separator) {
|
|
48
|
-
return (
|
|
49
|
-
<ul
|
|
50
|
-
style={{
|
|
51
|
-
listStyle: 'disc',
|
|
52
|
-
listStylePosition: 'inside',
|
|
53
|
-
margin: 0,
|
|
54
|
-
paddingLeft: '8px',
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
57
|
-
{overallReason.split(separator).map((item, index) => (
|
|
58
|
-
<li key={index} style={{ fontSize: '12px', marginBottom: '2px' }}>
|
|
59
|
-
{item.trim()}
|
|
60
|
-
</li>
|
|
61
|
-
))}
|
|
62
|
-
</ul>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
return <p style={{ fontSize: '12px' }}>{overallReason}</p>;
|
|
66
|
-
})()}
|
|
73
|
+
{renderReasonContent(overallReason)}
|
|
67
74
|
</div>
|
|
68
75
|
)}
|
|
69
76
|
</div>
|
|
@@ -2,7 +2,9 @@ import { colors, fonts } from '../../theme';
|
|
|
2
2
|
import ExpandableSection from './ExpandableSection';
|
|
3
3
|
|
|
4
4
|
export default function RecommendationsSection({ recommendations }) {
|
|
5
|
-
if (!recommendations || recommendations.length === 0)
|
|
5
|
+
if (!recommendations || recommendations.length === 0) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
return (
|
|
8
10
|
<ExpandableSection
|
|
@@ -13,24 +15,24 @@ export default function RecommendationsSection({ recommendations }) {
|
|
|
13
15
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
14
16
|
{recommendations.map((recommendation, index) => (
|
|
15
17
|
<div
|
|
16
|
-
key={index}
|
|
18
|
+
key={`recommendation-${recommendation.type || index}-${index}`}
|
|
17
19
|
style={{
|
|
18
20
|
display: 'flex',
|
|
19
21
|
alignItems: 'flex-start',
|
|
20
22
|
gap: '8px',
|
|
21
23
|
padding: '8px',
|
|
22
|
-
background: colors.accentBlue
|
|
24
|
+
background: `${colors.accentBlue}10`,
|
|
23
25
|
borderRadius: '6px',
|
|
24
26
|
border: `1px solid ${colors.accentBlue}20`,
|
|
25
27
|
transition: 'all 0.15s',
|
|
26
28
|
}}
|
|
27
29
|
onMouseEnter={(e) => {
|
|
28
|
-
e.currentTarget.style.background = colors.accentBlue
|
|
29
|
-
e.currentTarget.style.borderColor = colors.accentBlue
|
|
30
|
+
e.currentTarget.style.background = `${colors.accentBlue}15`;
|
|
31
|
+
e.currentTarget.style.borderColor = `${colors.accentBlue}40`;
|
|
30
32
|
}}
|
|
31
33
|
onMouseLeave={(e) => {
|
|
32
|
-
e.currentTarget.style.background = colors.accentBlue
|
|
33
|
-
e.currentTarget.style.borderColor = colors.accentBlue
|
|
34
|
+
e.currentTarget.style.background = `${colors.accentBlue}10`;
|
|
35
|
+
e.currentTarget.style.borderColor = `${colors.accentBlue}20`;
|
|
34
36
|
}}
|
|
35
37
|
>
|
|
36
38
|
<div
|
|
@@ -40,7 +42,7 @@ export default function RecommendationsSection({ recommendations }) {
|
|
|
40
42
|
width: '16px',
|
|
41
43
|
height: '16px',
|
|
42
44
|
borderRadius: '50%',
|
|
43
|
-
background: colors.accentBlue
|
|
45
|
+
background: `${colors.accentBlue}30`,
|
|
44
46
|
border: `1px solid ${colors.accentBlue}60`,
|
|
45
47
|
display: 'flex',
|
|
46
48
|
alignItems: 'center',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { IconExternalLink, IconX } from '@tabler/icons-react';
|
|
1
2
|
import { colors, fonts } from '../../theme';
|
|
2
|
-
import { IconX, IconExternalLink } from '@tabler/icons-react';
|
|
3
3
|
|
|
4
4
|
export default function ScanDetailHeader({ scanId, serverName, onClose }) {
|
|
5
5
|
return (
|
|
@@ -63,6 +63,7 @@ export default function ScanDetailHeader({ scanId, serverName, onClose }) {
|
|
|
63
63
|
</a>
|
|
64
64
|
)}
|
|
65
65
|
<button
|
|
66
|
+
type="button"
|
|
66
67
|
onClick={onClose}
|
|
67
68
|
style={{
|
|
68
69
|
padding: '6px',
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
2
|
import AnalysisResult from './AnalysisResult';
|
|
3
|
-
import { normalizeScanData } from './scanDataUtils';
|
|
4
|
-
import ScanDetailHeader from './ScanDetailHeader';
|
|
5
3
|
import DebugInfoSection from './DebugInfoSection';
|
|
4
|
+
import RawDataSection from './RawDataSection';
|
|
5
|
+
import ScanDetailHeader from './ScanDetailHeader';
|
|
6
6
|
import ScanOverviewSection from './ScanOverviewSection';
|
|
7
7
|
import ServerInfoSection from './ServerInfoSection';
|
|
8
|
-
import
|
|
8
|
+
import { normalizeScanData } from './scanDataUtils';
|
|
9
9
|
|
|
10
10
|
export default function ScanDetailView({ scan, loading, onClose }) {
|
|
11
11
|
if (loading) {
|
|
@@ -120,7 +120,7 @@ export default function ScanDetailView({ scan, loading, onClose }) {
|
|
|
120
120
|
<div
|
|
121
121
|
style={{
|
|
122
122
|
padding: '12px',
|
|
123
|
-
background: colors.bgTertiary
|
|
123
|
+
background: `${colors.bgTertiary}80`,
|
|
124
124
|
borderRadius: '6px',
|
|
125
125
|
border: `1px solid ${colors.borderLight}`,
|
|
126
126
|
fontSize: '12px',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { colors, fonts } from '../../../theme';
|
|
2
1
|
import { IconRefresh } from '@tabler/icons-react';
|
|
2
|
+
import { colors, fonts } from '../../../theme';
|
|
3
3
|
|
|
4
4
|
export default function ScanListHeader({ scanCount, loading, onRefresh }) {
|
|
5
5
|
return (
|
|
@@ -23,6 +23,7 @@ export default function ScanListHeader({ scanCount, loading, onRefresh }) {
|
|
|
23
23
|
All Scans ({scanCount})
|
|
24
24
|
</h2>
|
|
25
25
|
<button
|
|
26
|
+
type="button"
|
|
26
27
|
onClick={onRefresh}
|
|
27
28
|
disabled={loading}
|
|
28
29
|
style={{
|
|
@@ -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 };
|