@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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { colors, fonts } from '../theme';
|
|
2
|
-
import { useMcpPlayground } from './McpPlayground/useMcpPlayground';
|
|
3
2
|
import LoadingModal from './McpPlayground/LoadingModal';
|
|
4
|
-
import ToolsSection from './McpPlayground/ToolsSection';
|
|
5
3
|
import PromptsSection from './McpPlayground/PromptsSection';
|
|
6
4
|
import ResourcesSection from './McpPlayground/ResourcesSection';
|
|
5
|
+
import ToolsSection from './McpPlayground/ToolsSection';
|
|
6
|
+
import { useMcpPlayground } from './McpPlayground/useMcpPlayground';
|
|
7
7
|
|
|
8
8
|
function McpPlayground() {
|
|
9
9
|
const {
|
|
@@ -41,6 +41,9 @@ function McpPlayground() {
|
|
|
41
41
|
handleCallTool,
|
|
42
42
|
handleGetPrompt,
|
|
43
43
|
handleReadResource,
|
|
44
|
+
availableServers,
|
|
45
|
+
selectedServer,
|
|
46
|
+
setSelectedServer,
|
|
44
47
|
} = useMcpPlayground();
|
|
45
48
|
|
|
46
49
|
return (
|
|
@@ -84,32 +87,111 @@ function McpPlayground() {
|
|
|
84
87
|
<div
|
|
85
88
|
style={{
|
|
86
89
|
display: 'flex',
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
flexDirection: 'column',
|
|
91
|
+
gap: '12px',
|
|
89
92
|
}}
|
|
90
93
|
>
|
|
91
|
-
{
|
|
92
|
-
<
|
|
93
|
-
key={section}
|
|
94
|
-
onClick={() => setActiveSection(section)}
|
|
94
|
+
{availableServers.length > 0 && (
|
|
95
|
+
<div
|
|
95
96
|
style={{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
borderBottom: `2px solid ${activeSection === section ? colors.accentBlue : 'transparent'}`,
|
|
100
|
-
color: activeSection === section ? colors.textPrimary : colors.textSecondary,
|
|
101
|
-
cursor: 'pointer',
|
|
102
|
-
fontSize: '13px',
|
|
103
|
-
fontFamily: fonts.body,
|
|
104
|
-
fontWeight: activeSection === section ? '500' : '400',
|
|
105
|
-
textTransform: 'capitalize',
|
|
106
|
-
borderRadius: '6px 6px 0 0',
|
|
107
|
-
transition: 'all 0.2s',
|
|
97
|
+
display: 'flex',
|
|
98
|
+
flexDirection: 'column',
|
|
99
|
+
gap: '8px',
|
|
108
100
|
}}
|
|
109
101
|
>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
<label
|
|
103
|
+
htmlFor="mcp-server-select"
|
|
104
|
+
style={{
|
|
105
|
+
fontSize: '13px',
|
|
106
|
+
fontFamily: fonts.body,
|
|
107
|
+
color: colors.textSecondary,
|
|
108
|
+
fontWeight: '500',
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Server:
|
|
112
|
+
</label>
|
|
113
|
+
<div
|
|
114
|
+
style={{
|
|
115
|
+
display: 'flex',
|
|
116
|
+
flexWrap: 'wrap',
|
|
117
|
+
gap: '8px',
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{availableServers.map((server) => (
|
|
121
|
+
<button
|
|
122
|
+
key={server}
|
|
123
|
+
type="button"
|
|
124
|
+
onClick={() => setSelectedServer(server)}
|
|
125
|
+
style={{
|
|
126
|
+
padding: '10px 18px',
|
|
127
|
+
background:
|
|
128
|
+
selectedServer === server ? colors.accentBlue : colors.bgSecondary,
|
|
129
|
+
border:
|
|
130
|
+
selectedServer === server
|
|
131
|
+
? `2px solid ${colors.accentBlue}`
|
|
132
|
+
: `1px solid ${colors.borderLight}`,
|
|
133
|
+
borderRadius: '8px',
|
|
134
|
+
color: selectedServer === server ? colors.textInverse : colors.textPrimary,
|
|
135
|
+
fontSize: '13px',
|
|
136
|
+
fontFamily: fonts.body,
|
|
137
|
+
fontWeight: selectedServer === server ? '600' : '500',
|
|
138
|
+
cursor: 'pointer',
|
|
139
|
+
transition: 'all 0.2s ease',
|
|
140
|
+
boxShadow:
|
|
141
|
+
selectedServer === server ? `0 2px 4px ${colors.shadowSm}` : 'none',
|
|
142
|
+
}}
|
|
143
|
+
onMouseEnter={(e) => {
|
|
144
|
+
if (selectedServer !== server) {
|
|
145
|
+
e.currentTarget.style.background = colors.bgHover;
|
|
146
|
+
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
147
|
+
e.currentTarget.style.boxShadow = `0 2px 4px ${colors.shadowSm}`;
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
onMouseLeave={(e) => {
|
|
151
|
+
if (selectedServer !== server) {
|
|
152
|
+
e.currentTarget.style.background = colors.bgSecondary;
|
|
153
|
+
e.currentTarget.style.borderColor = colors.borderLight;
|
|
154
|
+
e.currentTarget.style.boxShadow = 'none';
|
|
155
|
+
}
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
{server}
|
|
159
|
+
</button>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
<div
|
|
165
|
+
style={{
|
|
166
|
+
display: 'flex',
|
|
167
|
+
gap: '8px',
|
|
168
|
+
borderBottom: `1px solid ${colors.borderLight}`,
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
{['tools', 'prompts', 'resources'].map((section) => (
|
|
172
|
+
<button
|
|
173
|
+
key={section}
|
|
174
|
+
type="button"
|
|
175
|
+
onClick={() => setActiveSection(section)}
|
|
176
|
+
style={{
|
|
177
|
+
padding: '10px 18px',
|
|
178
|
+
background: activeSection === section ? colors.bgSecondary : 'transparent',
|
|
179
|
+
border: 'none',
|
|
180
|
+
borderBottom: `2px solid ${activeSection === section ? colors.accentBlue : 'transparent'}`,
|
|
181
|
+
color: activeSection === section ? colors.textPrimary : colors.textSecondary,
|
|
182
|
+
cursor: 'pointer',
|
|
183
|
+
fontSize: '13px',
|
|
184
|
+
fontFamily: fonts.body,
|
|
185
|
+
fontWeight: activeSection === section ? '500' : '400',
|
|
186
|
+
textTransform: 'capitalize',
|
|
187
|
+
borderRadius: '6px 6px 0 0',
|
|
188
|
+
transition: 'all 0.2s',
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
{section}
|
|
192
|
+
</button>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
113
195
|
</div>
|
|
114
196
|
|
|
115
197
|
<div style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { colors, fonts } from '../theme';
|
|
2
1
|
import { IconX } from '@tabler/icons-react';
|
|
2
|
+
import { colors, fonts } from '../theme';
|
|
3
3
|
|
|
4
4
|
function PacketDetailHeader({ request, onClose, matchingPair }) {
|
|
5
5
|
const formatBytes = (bytes) => {
|
|
6
|
-
if (bytes < 1024)
|
|
7
|
-
|
|
6
|
+
if (bytes < 1024) {
|
|
7
|
+
return `${bytes} B`;
|
|
8
|
+
}
|
|
9
|
+
if (bytes < 1024 * 1024) {
|
|
10
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
11
|
+
}
|
|
8
12
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
9
13
|
};
|
|
10
14
|
|
|
@@ -59,6 +63,7 @@ function PacketDetailHeader({ request, onClose, matchingPair }) {
|
|
|
59
63
|
</span>
|
|
60
64
|
</div>
|
|
61
65
|
<button
|
|
66
|
+
type="button"
|
|
62
67
|
onClick={onClose}
|
|
63
68
|
style={{
|
|
64
69
|
background: 'none',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { colors, fonts } from '../../theme';
|
|
2
1
|
import { IconDownload } from '@tabler/icons-react';
|
|
3
2
|
import anime from 'animejs';
|
|
3
|
+
import { colors, fonts } from '../../theme';
|
|
4
4
|
|
|
5
5
|
export default function ExportControls({ stats, onExport }) {
|
|
6
6
|
return (
|
|
@@ -50,6 +50,7 @@ export default function ExportControls({ stats, onExport }) {
|
|
|
50
50
|
}}
|
|
51
51
|
>
|
|
52
52
|
<button
|
|
53
|
+
type="button"
|
|
53
54
|
onClick={() => onExport('json')}
|
|
54
55
|
style={{
|
|
55
56
|
padding: '8px 14px',
|
|
@@ -17,7 +17,10 @@ const ChevronDown = ({ size = 14, color = 'currentColor', rotated = false }) =>
|
|
|
17
17
|
transform: rotated ? 'rotate(-90deg)' : 'rotate(0deg)',
|
|
18
18
|
transition: 'transform 0.2s ease',
|
|
19
19
|
}}
|
|
20
|
+
role="img"
|
|
21
|
+
aria-label="Chevron down icon"
|
|
20
22
|
>
|
|
23
|
+
<title>Chevron down icon</title>
|
|
21
24
|
<polyline points="6 9 12 15 18 9" />
|
|
22
25
|
</svg>
|
|
23
26
|
);
|
|
@@ -35,8 +38,15 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
|
|
|
35
38
|
marginBottom: '20px',
|
|
36
39
|
}}
|
|
37
40
|
>
|
|
38
|
-
<
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
39
43
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
44
|
+
onKeyDown={(e) => {
|
|
45
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
setIsExpanded(!isExpanded);
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
40
50
|
style={{
|
|
41
51
|
padding: '16px 20px',
|
|
42
52
|
background: isExpanded ? colors.bgCard : colors.bgSecondary,
|
|
@@ -47,6 +57,9 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
|
|
|
47
57
|
alignItems: 'center',
|
|
48
58
|
justifyContent: 'space-between',
|
|
49
59
|
transition: 'background-color 0.15s ease',
|
|
60
|
+
width: '100%',
|
|
61
|
+
border: 'none',
|
|
62
|
+
textAlign: 'left',
|
|
50
63
|
}}
|
|
51
64
|
onMouseEnter={(e) => {
|
|
52
65
|
e.currentTarget.style.background = colors.bgHover;
|
|
@@ -71,7 +84,7 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
|
|
|
71
84
|
<ChevronDown size={14} color={titleColor} rotated={!isExpanded} />
|
|
72
85
|
{title}
|
|
73
86
|
</div>
|
|
74
|
-
</
|
|
87
|
+
</button>
|
|
75
88
|
{isExpanded && <div style={{ padding: '20px' }}>{children}</div>}
|
|
76
89
|
</div>
|
|
77
90
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { colors, fonts, withOpacity } 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
9
|
export default function OrphanedResponseRow({ response, selected, firstRequestTime, onSelect }) {
|
|
@@ -14,6 +14,14 @@ export default function OrphanedResponseRow({ response, selected, firstRequestTi
|
|
|
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 orphaned response ${response.frame_number}`}
|
|
17
25
|
style={{
|
|
18
26
|
cursor: 'pointer',
|
|
19
27
|
background: isSelected ? colors.bgSelected : colors.bgUnpaired,
|
|
@@ -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>
|