@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,11 +1,11 @@
|
|
|
1
|
+
import { IconChevronDown } from '@tabler/icons-react';
|
|
1
2
|
import { colors, fonts, withOpacity } from '../../theme';
|
|
2
3
|
import {
|
|
3
|
-
formatRelativeTime,
|
|
4
4
|
formatDateTime,
|
|
5
|
-
|
|
5
|
+
formatRelativeTime,
|
|
6
6
|
getEndpoint,
|
|
7
|
+
getSourceDest,
|
|
7
8
|
} from '../../utils/requestUtils.js';
|
|
8
|
-
import { IconChevronDown } from '@tabler/icons-react';
|
|
9
9
|
|
|
10
10
|
const ChevronDown = ({ size = 12, rotated = false }) => (
|
|
11
11
|
<IconChevronDown
|
|
@@ -39,6 +39,14 @@ export default function RequestRowMain({
|
|
|
39
39
|
<>
|
|
40
40
|
<tr
|
|
41
41
|
onClick={() => onSelect(request)}
|
|
42
|
+
onKeyDown={(e) => {
|
|
43
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
onSelect(request);
|
|
46
|
+
}
|
|
47
|
+
}}
|
|
48
|
+
tabIndex={0}
|
|
49
|
+
aria-label={`Select request ${request.frame_number}`}
|
|
42
50
|
style={{
|
|
43
51
|
cursor: 'pointer',
|
|
44
52
|
background: isSelected
|
|
@@ -84,6 +92,7 @@ export default function RequestRowMain({
|
|
|
84
92
|
>
|
|
85
93
|
{hasResponse && (
|
|
86
94
|
<button
|
|
95
|
+
type="button"
|
|
87
96
|
onClick={(e) => {
|
|
88
97
|
e.stopPropagation();
|
|
89
98
|
onToggleExpand();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
2
|
import {
|
|
3
|
-
formatRelativeTime,
|
|
4
3
|
formatDateTime,
|
|
5
|
-
|
|
4
|
+
formatRelativeTime,
|
|
6
5
|
getEndpoint,
|
|
6
|
+
getSourceDest,
|
|
7
7
|
} from '../../utils/requestUtils.js';
|
|
8
8
|
|
|
9
|
-
export default function ResponseRow({ response, selected, firstRequestTime, onSelect
|
|
9
|
+
export default function ResponseRow({ response, selected, firstRequestTime, onSelect }) {
|
|
10
10
|
const isSelected = selected?.frame_number === response.frame_number;
|
|
11
11
|
const { source, dest } = getSourceDest(response);
|
|
12
12
|
const relativeTime = formatRelativeTime(response.timestamp_iso, firstRequestTime);
|
|
@@ -14,6 +14,14 @@ export default function ResponseRow({ response, selected, firstRequestTime, onSe
|
|
|
14
14
|
return (
|
|
15
15
|
<tr
|
|
16
16
|
onClick={() => onSelect(response)}
|
|
17
|
+
onKeyDown={(e) => {
|
|
18
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
onSelect(response);
|
|
21
|
+
}
|
|
22
|
+
}}
|
|
23
|
+
tabIndex={0}
|
|
24
|
+
aria-label={`Select response ${response.frame_number}`}
|
|
17
25
|
style={{
|
|
18
26
|
cursor: 'pointer',
|
|
19
27
|
background:
|
|
@@ -12,17 +12,23 @@ function RequestRow({
|
|
|
12
12
|
onToggleExpand = () => {},
|
|
13
13
|
}) {
|
|
14
14
|
// Support both pair prop (new) and request prop (legacy for grouped views)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const extractRequestResponse = (pair, requestProp) => {
|
|
16
|
+
if (pair) {
|
|
17
|
+
return { request: pair.request, response: pair.response };
|
|
18
|
+
}
|
|
19
|
+
if (requestProp) {
|
|
20
|
+
return { request: requestProp, response: null };
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const data = extractRequestResponse(pair, requestProp);
|
|
26
|
+
if (!data) {
|
|
23
27
|
return null; // No valid data
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
const { request, response } = data;
|
|
31
|
+
|
|
26
32
|
// Check if this is an unpaired request or response
|
|
27
33
|
const isUnpaired = !request || !response;
|
|
28
34
|
|
|
@@ -38,7 +44,9 @@ function RequestRow({
|
|
|
38
44
|
);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
if (!request)
|
|
47
|
+
if (!request) {
|
|
48
|
+
return null; // Only show rows that have a request
|
|
49
|
+
}
|
|
42
50
|
|
|
43
51
|
const hasResponse = !!response;
|
|
44
52
|
|
|
@@ -29,6 +29,7 @@ function ServerControl({ status, loading, onStart, onStop, canStart }) {
|
|
|
29
29
|
<div style={{ display: 'flex', gap: '12px', alignItems: 'center', flexWrap: 'wrap' }}>
|
|
30
30
|
{status.running ? (
|
|
31
31
|
<button
|
|
32
|
+
type="button"
|
|
32
33
|
onClick={onStop}
|
|
33
34
|
disabled={loading}
|
|
34
35
|
style={{
|
|
@@ -60,6 +61,7 @@ function ServerControl({ status, loading, onStart, onStop, canStart }) {
|
|
|
60
61
|
</button>
|
|
61
62
|
) : (
|
|
62
63
|
<button
|
|
64
|
+
type="button"
|
|
63
65
|
data-tour="start-button"
|
|
64
66
|
onClick={onStart}
|
|
65
67
|
disabled={loading || !canStart}
|
|
@@ -122,7 +124,7 @@ function ServerControl({ status, loading, onStart, onStop, canStart }) {
|
|
|
122
124
|
fontFamily: fonts.body,
|
|
123
125
|
}}
|
|
124
126
|
>
|
|
125
|
-
{status.running ? `Running (PID: ${status.pid})` : 'Stopped'}
|
|
127
|
+
{status.running ? (status.pid ? `Running (PID: ${status.pid})` : 'Running') : 'Stopped'}
|
|
126
128
|
</span>
|
|
127
129
|
</div>
|
|
128
130
|
</div>
|
|
@@ -40,6 +40,7 @@ function ServiceSelector({ services, selectedServices, onSelectionChange }) {
|
|
|
40
40
|
}}
|
|
41
41
|
>
|
|
42
42
|
<button
|
|
43
|
+
type="button"
|
|
43
44
|
onClick={() => {
|
|
44
45
|
onSelectionChange(new Set(services.map((s) => s.name)));
|
|
45
46
|
}}
|
|
@@ -56,6 +57,7 @@ function ServiceSelector({ services, selectedServices, onSelectionChange }) {
|
|
|
56
57
|
Select All
|
|
57
58
|
</button>
|
|
58
59
|
<button
|
|
60
|
+
type="button"
|
|
59
61
|
onClick={() => {
|
|
60
62
|
onSelectionChange(new Set());
|
|
61
63
|
}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
2
|
import ExpandableSection from './ExpandableSection';
|
|
3
3
|
import FindingsTable from './FindingsTable';
|
|
4
|
+
import NotablePatternsSection from './NotablePatternsSection';
|
|
4
5
|
import OverallSummarySection from './OverallSummarySection';
|
|
5
6
|
import RecommendationsSection from './RecommendationsSection';
|
|
6
|
-
import NotablePatternsSection from './NotablePatternsSection';
|
|
7
7
|
|
|
8
8
|
export default function AnalysisResult({ analysis }) {
|
|
9
9
|
if (!analysis) {
|
|
@@ -11,7 +11,7 @@ export default function AnalysisResult({ analysis }) {
|
|
|
11
11
|
<div
|
|
12
12
|
style={{
|
|
13
13
|
padding: '12px',
|
|
14
|
-
background: colors.bgTertiary
|
|
14
|
+
background: `${colors.bgTertiary}80`,
|
|
15
15
|
borderRadius: '6px',
|
|
16
16
|
border: `1px solid ${colors.borderLight}`,
|
|
17
17
|
fontSize: '12px',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CheckIcon, CacheIcon, ExternalLinkIcon } from '../../SmartScanIcons';
|
|
2
1
|
import { IconEye } from '@tabler/icons-react';
|
|
3
2
|
import { colors, fonts } from '../../../theme';
|
|
3
|
+
import { CacheIcon, CheckIcon, ExternalLinkIcon } from '../../SmartScanIcons';
|
|
4
4
|
import { getRiskLevelColor } from '../utils';
|
|
5
5
|
|
|
6
6
|
export default function BatchResultItem({ result, onViewScan }) {
|
|
@@ -64,6 +64,7 @@ export default function BatchResultItem({ result, onViewScan }) {
|
|
|
64
64
|
)}
|
|
65
65
|
{onViewScan && result.data && (
|
|
66
66
|
<button
|
|
67
|
+
type="button"
|
|
67
68
|
onClick={() => onViewScan(result.data)}
|
|
68
69
|
style={{
|
|
69
70
|
display: 'inline-flex',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CheckIcon, CacheIcon } from '../../SmartScanIcons';
|
|
2
1
|
import { colors, fonts } from '../../../theme';
|
|
2
|
+
import { CacheIcon, CheckIcon } from '../../SmartScanIcons';
|
|
3
3
|
|
|
4
4
|
export default function BatchResultsHeader({ scanResults }) {
|
|
5
5
|
const cachedCount = scanResults.filter((r) => r.cached).length;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { colors } from '../../theme';
|
|
2
|
-
import BatchResultsHeader from './BatchResultsDisplay/BatchResultsHeader';
|
|
3
2
|
import BatchResultItem from './BatchResultsDisplay/BatchResultItem';
|
|
3
|
+
import BatchResultsHeader from './BatchResultsDisplay/BatchResultsHeader';
|
|
4
4
|
|
|
5
5
|
export default function BatchResultsDisplay({ scanResults, onViewScan }) {
|
|
6
|
-
if (scanResults.length === 0)
|
|
6
|
+
if (scanResults.length === 0) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
7
9
|
|
|
8
10
|
return (
|
|
9
11
|
<div
|
|
@@ -18,7 +20,11 @@ export default function BatchResultsDisplay({ scanResults, onViewScan }) {
|
|
|
18
20
|
<BatchResultsHeader scanResults={scanResults} />
|
|
19
21
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
20
22
|
{scanResults.map((result, idx) => (
|
|
21
|
-
<BatchResultItem
|
|
23
|
+
<BatchResultItem
|
|
24
|
+
key={result.scanId || `batch-result-${idx}`}
|
|
25
|
+
result={result}
|
|
26
|
+
onViewScan={onViewScan}
|
|
27
|
+
/>
|
|
22
28
|
))}
|
|
23
29
|
</div>
|
|
24
30
|
</div>
|
|
@@ -24,7 +24,10 @@ export default function EmptyState() {
|
|
|
24
24
|
strokeLinecap="round"
|
|
25
25
|
strokeLinejoin="round"
|
|
26
26
|
style={{ opacity: 0.5 }}
|
|
27
|
+
role="img"
|
|
28
|
+
aria-label="Empty state icon"
|
|
27
29
|
>
|
|
30
|
+
<title>Empty state icon</title>
|
|
28
31
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
29
32
|
<path d="M9 12l2 2 4-4" />
|
|
30
33
|
</svg>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { AlertIcon } from '../SmartScanIcons';
|
|
2
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
+
import { AlertIcon } from '../SmartScanIcons';
|
|
3
3
|
|
|
4
4
|
export default function ErrorDisplay({ error }) {
|
|
5
|
-
if (!error)
|
|
5
|
+
if (!error) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
return (
|
|
8
10
|
<div
|
|
@@ -14,6 +14,7 @@ export default function ExpandableSection({ title, count, children, defaultExpan
|
|
|
14
14
|
}}
|
|
15
15
|
>
|
|
16
16
|
<button
|
|
17
|
+
type="button"
|
|
17
18
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
18
19
|
style={{
|
|
19
20
|
width: '100%',
|
|
@@ -73,7 +74,10 @@ export default function ExpandableSection({ title, count, children, defaultExpan
|
|
|
73
74
|
fill="none"
|
|
74
75
|
stroke="currentColor"
|
|
75
76
|
viewBox="0 0 24 24"
|
|
77
|
+
role="img"
|
|
78
|
+
aria-label="Chevron icon"
|
|
76
79
|
>
|
|
80
|
+
<title>Chevron icon</title>
|
|
77
81
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
78
82
|
</svg>
|
|
79
83
|
</button>
|
|
@@ -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={{
|