@mcp-shark/mcp-shark 1.4.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/LICENSE +85 -0
- package/README.md +724 -0
- package/bin/mcp-shark.js +93 -0
- package/mcp-server/.editorconfig +15 -0
- package/mcp-server/.prettierignore +11 -0
- package/mcp-server/.prettierrc +12 -0
- package/mcp-server/README.md +280 -0
- package/mcp-server/commitlint.config.cjs +42 -0
- package/mcp-server/eslint.config.js +131 -0
- package/mcp-server/lib/auditor/audit.js +228 -0
- package/mcp-server/lib/common/error.js +15 -0
- package/mcp-server/lib/server/external/all.js +32 -0
- package/mcp-server/lib/server/external/config.js +59 -0
- package/mcp-server/lib/server/external/kv.js +102 -0
- package/mcp-server/lib/server/external/single/client.js +35 -0
- package/mcp-server/lib/server/external/single/request.js +49 -0
- package/mcp-server/lib/server/external/single/run.js +75 -0
- package/mcp-server/lib/server/external/single/transport.js +57 -0
- package/mcp-server/lib/server/internal/handlers/common.js +20 -0
- package/mcp-server/lib/server/internal/handlers/error.js +7 -0
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +22 -0
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +12 -0
- package/mcp-server/lib/server/internal/handlers/resources-list.js +12 -0
- package/mcp-server/lib/server/internal/handlers/resources-read.js +19 -0
- package/mcp-server/lib/server/internal/handlers/tools-call.js +37 -0
- package/mcp-server/lib/server/internal/handlers/tools-list.js +14 -0
- package/mcp-server/lib/server/internal/run.js +49 -0
- package/mcp-server/lib/server/internal/server.js +63 -0
- package/mcp-server/lib/server/internal/session.js +39 -0
- package/mcp-server/mcp-shark.js +72 -0
- package/mcp-server/package-lock.json +4784 -0
- package/mcp-server/package.json +30 -0
- package/package.json +103 -0
- package/ui/README.md +212 -0
- package/ui/index.html +16 -0
- package/ui/package-lock.json +3574 -0
- package/ui/package.json +12 -0
- package/ui/paths.js +282 -0
- package/ui/public/og-image.png +0 -0
- package/ui/server/routes/backups.js +251 -0
- package/ui/server/routes/composite.js +244 -0
- package/ui/server/routes/config.js +175 -0
- package/ui/server/routes/conversations.js +25 -0
- package/ui/server/routes/help.js +43 -0
- package/ui/server/routes/logs.js +32 -0
- package/ui/server/routes/playground.js +152 -0
- package/ui/server/routes/requests.js +235 -0
- package/ui/server/routes/sessions.js +27 -0
- package/ui/server/routes/smartscan/discover.js +117 -0
- package/ui/server/routes/smartscan/scans/clearCache.js +22 -0
- package/ui/server/routes/smartscan/scans/createBatchScans.js +123 -0
- package/ui/server/routes/smartscan/scans/createScan.js +42 -0
- package/ui/server/routes/smartscan/scans/getCachedResults.js +51 -0
- package/ui/server/routes/smartscan/scans/getScan.js +41 -0
- package/ui/server/routes/smartscan/scans/listScans.js +24 -0
- package/ui/server/routes/smartscan/scans.js +13 -0
- package/ui/server/routes/smartscan/token.js +56 -0
- package/ui/server/routes/smartscan/transport.js +53 -0
- package/ui/server/routes/smartscan.js +24 -0
- package/ui/server/routes/statistics.js +83 -0
- package/ui/server/utils/config-update.js +212 -0
- package/ui/server/utils/config.js +98 -0
- package/ui/server/utils/paths.js +23 -0
- package/ui/server/utils/port.js +28 -0
- package/ui/server/utils/process.js +80 -0
- package/ui/server/utils/scan-cache/all-results.js +180 -0
- package/ui/server/utils/scan-cache/directory.js +35 -0
- package/ui/server/utils/scan-cache/file-operations.js +104 -0
- package/ui/server/utils/scan-cache/hash.js +47 -0
- package/ui/server/utils/scan-cache/server-operations.js +80 -0
- package/ui/server/utils/scan-cache.js +12 -0
- package/ui/server/utils/serialization.js +13 -0
- package/ui/server/utils/smartscan-token.js +42 -0
- package/ui/server.js +199 -0
- package/ui/src/App.jsx +153 -0
- package/ui/src/CompositeLogs.jsx +164 -0
- package/ui/src/CompositeSetup.jsx +285 -0
- package/ui/src/HelpGuide/HelpGuideContent.jsx +118 -0
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +58 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +56 -0
- package/ui/src/HelpGuide.jsx +65 -0
- package/ui/src/IntroTour.jsx +140 -0
- package/ui/src/LogDetail.jsx +122 -0
- package/ui/src/LogTable.jsx +242 -0
- package/ui/src/PacketDetail.jsx +190 -0
- package/ui/src/PacketFilters.jsx +222 -0
- package/ui/src/PacketList.jsx +183 -0
- package/ui/src/SmartScan.jsx +178 -0
- package/ui/src/TabNavigation.jsx +143 -0
- package/ui/src/components/App/HelpButton.jsx +64 -0
- package/ui/src/components/App/TrafficTab.jsx +69 -0
- package/ui/src/components/App/useAppState.js +163 -0
- package/ui/src/components/BackupList.jsx +192 -0
- package/ui/src/components/CollapsibleSection.jsx +82 -0
- package/ui/src/components/ConfigFileSection.jsx +84 -0
- package/ui/src/components/ConfigViewerModal.jsx +141 -0
- package/ui/src/components/ConfirmationModal.jsx +129 -0
- package/ui/src/components/DetailsTab/BodySection.jsx +27 -0
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +70 -0
- package/ui/src/components/DetailsTab/HeadersSection.jsx +25 -0
- package/ui/src/components/DetailsTab/InfoSection.jsx +28 -0
- package/ui/src/components/DetailsTab/NetworkInfoSection.jsx +63 -0
- package/ui/src/components/DetailsTab/ProtocolInfoSection.jsx +75 -0
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +46 -0
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +66 -0
- package/ui/src/components/DetailsTab.jsx +31 -0
- package/ui/src/components/DetectedPathsList.jsx +171 -0
- package/ui/src/components/FileInput.jsx +144 -0
- package/ui/src/components/GroupHeader.jsx +76 -0
- package/ui/src/components/GroupedByMcpView.jsx +103 -0
- package/ui/src/components/GroupedByServerView.jsx +134 -0
- package/ui/src/components/GroupedBySessionView.jsx +127 -0
- package/ui/src/components/GroupedViews.jsx +2 -0
- package/ui/src/components/HexTab.jsx +188 -0
- package/ui/src/components/LogsDisplay.jsx +93 -0
- package/ui/src/components/LogsToolbar.jsx +193 -0
- package/ui/src/components/McpPlayground/LoadingModal.jsx +113 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +125 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +48 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +45 -0
- package/ui/src/components/McpPlayground/PromptsSection.jsx +106 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +89 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +59 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +45 -0
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +91 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +125 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +48 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +45 -0
- package/ui/src/components/McpPlayground/ToolsSection.jsx +107 -0
- package/ui/src/components/McpPlayground/common/EmptyState.jsx +17 -0
- package/ui/src/components/McpPlayground/common/ErrorState.jsx +17 -0
- package/ui/src/components/McpPlayground/common/LoadingState.jsx +17 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +280 -0
- package/ui/src/components/McpPlayground.jsx +171 -0
- package/ui/src/components/MessageDisplay.jsx +28 -0
- package/ui/src/components/PacketDetailHeader.jsx +88 -0
- package/ui/src/components/PacketFilters/ExportControls.jsx +126 -0
- package/ui/src/components/PacketFilters/FilterInput.jsx +59 -0
- package/ui/src/components/RawTab.jsx +142 -0
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +155 -0
- package/ui/src/components/RequestRow/RequestRowMain.jsx +240 -0
- package/ui/src/components/RequestRow/ResponseRow.jsx +158 -0
- package/ui/src/components/RequestRow.jsx +70 -0
- package/ui/src/components/ServerControl.jsx +133 -0
- package/ui/src/components/ServiceSelector.jsx +209 -0
- package/ui/src/components/SetupHeader.jsx +30 -0
- package/ui/src/components/SharkLogo.jsx +21 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +64 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +215 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +94 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +26 -0
- package/ui/src/components/SmartScan/DebugInfoSection.jsx +53 -0
- package/ui/src/components/SmartScan/EmptyState.jsx +57 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +48 -0
- package/ui/src/components/SmartScan/ExpandableSection.jsx +93 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +257 -0
- package/ui/src/components/SmartScan/ListViewContent.jsx +75 -0
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +75 -0
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +72 -0
- package/ui/src/components/SmartScan/RawDataSection.jsx +52 -0
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +78 -0
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +92 -0
- package/ui/src/components/SmartScan/ScanDetailView.jsx +141 -0
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +49 -0
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +201 -0
- package/ui/src/components/SmartScan/ScanListView.jsx +73 -0
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +123 -0
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +35 -0
- package/ui/src/components/SmartScan/ScanViewContent.jsx +68 -0
- package/ui/src/components/SmartScan/ScanningProgress.jsx +47 -0
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +43 -0
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +207 -0
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +269 -0
- package/ui/src/components/SmartScan/SmartScanControls.jsx +290 -0
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +77 -0
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +57 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +34 -0
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +121 -0
- package/ui/src/components/SmartScan/hooks/useScanList.js +193 -0
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +87 -0
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +26 -0
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +53 -0
- package/ui/src/components/SmartScan/scanDataUtils.js +98 -0
- package/ui/src/components/SmartScan/useSmartScan.js +72 -0
- package/ui/src/components/SmartScan/utils.js +19 -0
- package/ui/src/components/SmartScanIcons.jsx +58 -0
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +111 -0
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +140 -0
- package/ui/src/components/TabNavigation.jsx +97 -0
- package/ui/src/components/TabNavigationIcons.jsx +40 -0
- package/ui/src/components/TableHeader.jsx +164 -0
- package/ui/src/components/TourOverlay.jsx +117 -0
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +117 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +70 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +45 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +108 -0
- package/ui/src/components/TourTooltip.jsx +83 -0
- package/ui/src/components/ViewModeTabs.jsx +91 -0
- package/ui/src/components/WhatThisDoesSection.jsx +61 -0
- package/ui/src/config/tourSteps.jsx +141 -0
- package/ui/src/hooks/useAnimation.js +92 -0
- package/ui/src/hooks/useConfigManagement.js +124 -0
- package/ui/src/hooks/useServiceExtraction.js +51 -0
- package/ui/src/index.css +42 -0
- package/ui/src/main.jsx +10 -0
- package/ui/src/theme.js +65 -0
- package/ui/src/utils/animations.js +170 -0
- package/ui/src/utils/groupingUtils.js +93 -0
- package/ui/src/utils/hexUtils.js +24 -0
- package/ui/src/utils/mcpGroupingUtils.js +262 -0
- package/ui/src/utils/requestUtils.js +297 -0
- package/ui/vite.config.js +18 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export const tourSteps = [
|
|
4
|
+
{
|
|
5
|
+
target: '[data-tour="tabs"]',
|
|
6
|
+
title: 'Welcome to MCP Shark!',
|
|
7
|
+
content: (
|
|
8
|
+
<div>
|
|
9
|
+
<p style={{ margin: '0 0 12px 0' }}>
|
|
10
|
+
MCP Shark is a powerful tool for monitoring and analyzing Model Context Protocol (MCP)
|
|
11
|
+
communications. Let's get you started!
|
|
12
|
+
</p>
|
|
13
|
+
<p style={{ margin: 0 }}>
|
|
14
|
+
First, you'll need to set up the MCP Shark server. Click on the{' '}
|
|
15
|
+
<strong>MCP Server Setup</strong> tab to begin.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
),
|
|
19
|
+
position: 'bottom',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
target: '[data-tour="setup-tab"]',
|
|
23
|
+
title: 'Step 1: Open MCP Server Setup',
|
|
24
|
+
content: (
|
|
25
|
+
<div>
|
|
26
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
27
|
+
Click on the <strong>MCP Server Setup</strong> tab to configure and start the MCP Shark
|
|
28
|
+
server.
|
|
29
|
+
</p>
|
|
30
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
31
|
+
This is where you'll configure your MCP servers and start monitoring.
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
position: 'bottom',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
target: '[data-tour="detected-editors"]',
|
|
39
|
+
title: 'Step 2: Select Your Configuration',
|
|
40
|
+
content: (
|
|
41
|
+
<div>
|
|
42
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
43
|
+
MCP Shark automatically detects your IDE's MCP configuration files. You have two options:
|
|
44
|
+
</p>
|
|
45
|
+
<ul style={{ margin: '0 0 8px 0', paddingLeft: '20px', fontSize: '13px' }}>
|
|
46
|
+
<li>
|
|
47
|
+
Click on any <strong>detected editor</strong> (like Cursor or Windsurf) to use its
|
|
48
|
+
config
|
|
49
|
+
</li>
|
|
50
|
+
<li>
|
|
51
|
+
Or click <strong>"Select File"</strong> to upload your own config file
|
|
52
|
+
</li>
|
|
53
|
+
</ul>
|
|
54
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
55
|
+
When you click a detected editor, the file path will automatically populate in the text
|
|
56
|
+
box.
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
position: 'bottom',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
target: '[data-tour="select-file"]',
|
|
64
|
+
title: 'Alternative: Upload Your Config',
|
|
65
|
+
content: (
|
|
66
|
+
<div>
|
|
67
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
68
|
+
If you prefer, you can click <strong>"Select File"</strong> to upload your MCP
|
|
69
|
+
configuration file directly.
|
|
70
|
+
</p>
|
|
71
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
72
|
+
Or manually enter the file path in the text box next to it.
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
position: 'bottom',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
target: '[data-tour="start-button"]',
|
|
80
|
+
title: 'Step 3: Start MCP Shark',
|
|
81
|
+
content: (
|
|
82
|
+
<div>
|
|
83
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
84
|
+
Once you've selected a configuration file (either from detected editors or uploaded),
|
|
85
|
+
click <strong>"Start MCP Shark"</strong> to begin monitoring.
|
|
86
|
+
</p>
|
|
87
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
88
|
+
The server will start and begin capturing all MCP traffic between your IDE and servers.
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
),
|
|
92
|
+
position: 'top',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
target: '[data-tour="traffic-tab"]',
|
|
96
|
+
title: 'View Your Traffic',
|
|
97
|
+
content: (
|
|
98
|
+
<div>
|
|
99
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
100
|
+
After starting the server, switch to the <strong>Traffic Capture</strong> tab to see all
|
|
101
|
+
HTTP requests and responses in real-time.
|
|
102
|
+
</p>
|
|
103
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
104
|
+
You can view traffic as a flat list, grouped by session, or grouped by server.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
),
|
|
108
|
+
position: 'bottom',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
target: '[data-tour="smart-scan-tab"]',
|
|
112
|
+
title: 'Smart Scan - Security Analysis',
|
|
113
|
+
content: (
|
|
114
|
+
<div>
|
|
115
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
116
|
+
The <strong>Smart Scan</strong> tab provides AI-powered security analysis for your MCP
|
|
117
|
+
servers. Discover servers, run security scans, and get detailed vulnerability reports.
|
|
118
|
+
</p>
|
|
119
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
120
|
+
Scan results are cached automatically, so you won't waste API calls on unchanged servers.
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
124
|
+
position: 'bottom',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
target: '[data-tour="help-button"]',
|
|
128
|
+
title: 'Need Help?',
|
|
129
|
+
content: (
|
|
130
|
+
<div>
|
|
131
|
+
<p style={{ margin: '0 0 8px 0' }}>
|
|
132
|
+
Click the <strong>Start Tour</strong> button anytime to restart this guide or get help.
|
|
133
|
+
</p>
|
|
134
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#858585' }}>
|
|
135
|
+
You're all set! Start by configuring your MCP server, then watch the traffic flow.
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
),
|
|
139
|
+
position: 'left',
|
|
140
|
+
},
|
|
141
|
+
];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import anime from 'animejs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React hook for animating elements on mount/unmount
|
|
6
|
+
*/
|
|
7
|
+
export const useAnimation = (animationFn, deps = []) => {
|
|
8
|
+
const elementRef = useRef(null);
|
|
9
|
+
const animationRef = useRef(null);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (elementRef.current && animationFn) {
|
|
13
|
+
animationRef.current = animationFn(elementRef.current);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
if (animationRef.current) {
|
|
18
|
+
anime.remove(elementRef.current);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}, deps);
|
|
22
|
+
|
|
23
|
+
return elementRef;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hook for animating on mount
|
|
28
|
+
*/
|
|
29
|
+
export const useMountAnimation = (options = {}) => {
|
|
30
|
+
const elementRef = useRef(null);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (elementRef.current) {
|
|
34
|
+
anime({
|
|
35
|
+
targets: elementRef.current,
|
|
36
|
+
opacity: [0, 1],
|
|
37
|
+
translateY: [20, 0],
|
|
38
|
+
duration: options.duration || 400,
|
|
39
|
+
easing: options.easing || 'easeOutExpo',
|
|
40
|
+
...options,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
return elementRef;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hook for animating on unmount
|
|
50
|
+
*/
|
|
51
|
+
export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) => {
|
|
52
|
+
const elementRef = useRef(null);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (shouldUnmount && elementRef.current) {
|
|
56
|
+
anime({
|
|
57
|
+
targets: elementRef.current,
|
|
58
|
+
opacity: [1, 0],
|
|
59
|
+
translateY: [0, -20],
|
|
60
|
+
duration: options.duration || 300,
|
|
61
|
+
easing: options.easing || 'easeInExpo',
|
|
62
|
+
complete: onComplete,
|
|
63
|
+
...options,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}, [shouldUnmount, onComplete]);
|
|
67
|
+
|
|
68
|
+
return elementRef;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Hook for staggered list animations
|
|
73
|
+
*/
|
|
74
|
+
export const useStaggerAnimation = (items, options = {}) => {
|
|
75
|
+
const containerRef = useRef(null);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (containerRef.current && items.length > 0) {
|
|
79
|
+
anime({
|
|
80
|
+
targets: containerRef.current.children,
|
|
81
|
+
opacity: [0, 1],
|
|
82
|
+
translateY: [20, 0],
|
|
83
|
+
duration: options.duration || 400,
|
|
84
|
+
delay: anime.stagger(options.delay || 50),
|
|
85
|
+
easing: options.easing || 'easeOutExpo',
|
|
86
|
+
...options,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}, [items.length]);
|
|
90
|
+
|
|
91
|
+
return containerRef;
|
|
92
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useConfigManagement() {
|
|
4
|
+
const [detectedPaths, setDetectedPaths] = useState([]);
|
|
5
|
+
const [detecting, setDetecting] = useState(true);
|
|
6
|
+
const [backups, setBackups] = useState([]);
|
|
7
|
+
const [loadingBackups, setLoadingBackups] = useState(false);
|
|
8
|
+
const [viewingConfig, setViewingConfig] = useState(null);
|
|
9
|
+
const [configContent, setConfigContent] = useState(null);
|
|
10
|
+
const [loadingConfig, setLoadingConfig] = useState(false);
|
|
11
|
+
const [viewingBackup, setViewingBackup] = useState(null);
|
|
12
|
+
const [backupContent, setBackupContent] = useState(null);
|
|
13
|
+
const [loadingBackup, setLoadingBackup] = useState(false);
|
|
14
|
+
|
|
15
|
+
const detectConfigPaths = async () => {
|
|
16
|
+
setDetecting(true);
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch('/api/config/detect');
|
|
19
|
+
const data = await res.json();
|
|
20
|
+
setDetectedPaths(data.detected || []);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error('Failed to detect config paths:', err);
|
|
23
|
+
} finally {
|
|
24
|
+
setDetecting(false);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const loadBackups = async () => {
|
|
29
|
+
setLoadingBackups(true);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch('/api/config/backups');
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
setBackups(data.backups || []);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error('Failed to load backups:', err);
|
|
36
|
+
} finally {
|
|
37
|
+
setLoadingBackups(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleViewConfig = async (filePath) => {
|
|
42
|
+
setLoadingConfig(true);
|
|
43
|
+
setViewingConfig(filePath);
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(`/api/config/read?filePath=${encodeURIComponent(filePath)}`);
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
if (res.ok) {
|
|
48
|
+
setConfigContent(data);
|
|
49
|
+
} else {
|
|
50
|
+
setConfigContent(null);
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
setConfigContent(null);
|
|
54
|
+
} finally {
|
|
55
|
+
setLoadingConfig(false);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleViewBackup = async (backupPath) => {
|
|
60
|
+
setLoadingBackup(true);
|
|
61
|
+
setViewingBackup(backupPath);
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(
|
|
64
|
+
`/api/config/backup/view?backupPath=${encodeURIComponent(backupPath)}`
|
|
65
|
+
);
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
if (res.ok) {
|
|
68
|
+
setBackupContent(data);
|
|
69
|
+
} else {
|
|
70
|
+
setBackupContent(null);
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setBackupContent(null);
|
|
74
|
+
} finally {
|
|
75
|
+
setLoadingBackup(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleDeleteBackup = async (backupPath) => {
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch('/api/config/backup/delete', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({ backupPath }),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (res.ok) {
|
|
88
|
+
await loadBackups();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error('Failed to delete backup:', err);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
detectConfigPaths();
|
|
100
|
+
loadBackups();
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
detectedPaths,
|
|
105
|
+
detecting,
|
|
106
|
+
detectConfigPaths,
|
|
107
|
+
backups,
|
|
108
|
+
loadingBackups,
|
|
109
|
+
loadBackups,
|
|
110
|
+
viewingConfig,
|
|
111
|
+
configContent,
|
|
112
|
+
loadingConfig,
|
|
113
|
+
handleViewConfig,
|
|
114
|
+
setViewingConfig,
|
|
115
|
+
setConfigContent,
|
|
116
|
+
viewingBackup,
|
|
117
|
+
backupContent,
|
|
118
|
+
loadingBackup,
|
|
119
|
+
handleViewBackup,
|
|
120
|
+
handleDeleteBackup,
|
|
121
|
+
setViewingBackup,
|
|
122
|
+
setBackupContent,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useServiceExtraction(fileContent, filePath) {
|
|
4
|
+
const [services, setServices] = useState([]);
|
|
5
|
+
const [selectedServices, setSelectedServices] = useState(new Set());
|
|
6
|
+
const [loadingServices, setLoadingServices] = useState(false);
|
|
7
|
+
|
|
8
|
+
const extractServices = useCallback(async () => {
|
|
9
|
+
if (!fileContent && !filePath) {
|
|
10
|
+
setServices([]);
|
|
11
|
+
setSelectedServices(new Set());
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setLoadingServices(true);
|
|
16
|
+
try {
|
|
17
|
+
const payload = fileContent ? { fileContent } : { filePath };
|
|
18
|
+
const res = await fetch('/api/config/services', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(payload),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (res.ok && data.services) {
|
|
26
|
+
setServices(data.services);
|
|
27
|
+
setSelectedServices(new Set(data.services.map((s) => s.name)));
|
|
28
|
+
} else {
|
|
29
|
+
setServices([]);
|
|
30
|
+
setSelectedServices(new Set());
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error('Failed to extract services:', err);
|
|
34
|
+
setServices([]);
|
|
35
|
+
setSelectedServices(new Set());
|
|
36
|
+
} finally {
|
|
37
|
+
setLoadingServices(false);
|
|
38
|
+
}
|
|
39
|
+
}, [fileContent, filePath]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (fileContent || filePath) {
|
|
43
|
+
extractServices();
|
|
44
|
+
} else {
|
|
45
|
+
setServices([]);
|
|
46
|
+
setSelectedServices(new Set());
|
|
47
|
+
}
|
|
48
|
+
}, [fileContent, filePath, extractServices]);
|
|
49
|
+
|
|
50
|
+
return { services, selectedServices, setSelectedServices, loadingServices };
|
|
51
|
+
}
|
package/ui/src/index.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;500&display=swap');
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-family:
|
|
11
|
+
'Roboto',
|
|
12
|
+
-apple-system,
|
|
13
|
+
BlinkMacSystemFont,
|
|
14
|
+
'Segoe UI',
|
|
15
|
+
sans-serif;
|
|
16
|
+
background: #ffffff;
|
|
17
|
+
color: #202124;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
-webkit-font-smoothing: antialiased;
|
|
20
|
+
-moz-osx-font-smoothing: grayscale;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
code,
|
|
24
|
+
pre,
|
|
25
|
+
.monospace {
|
|
26
|
+
font-family: 'Roboto Mono', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#root {
|
|
30
|
+
height: 100vh;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@keyframes spin {
|
|
36
|
+
from {
|
|
37
|
+
transform: rotate(0deg);
|
|
38
|
+
}
|
|
39
|
+
to {
|
|
40
|
+
transform: rotate(360deg);
|
|
41
|
+
}
|
|
42
|
+
}
|
package/ui/src/main.jsx
ADDED
package/ui/src/theme.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Gmail-inspired clean and minimal color theme
|
|
2
|
+
export const colors = {
|
|
3
|
+
// Backgrounds - Gmail style
|
|
4
|
+
bgPrimary: '#ffffff', // Pure white (Gmail main background)
|
|
5
|
+
bgSecondary: '#f8f9fa', // Very light gray (Gmail sidebar/secondary areas)
|
|
6
|
+
bgTertiary: '#f1f3f4', // Light gray (hover states)
|
|
7
|
+
bgCard: '#ffffff', // Pure white for cards
|
|
8
|
+
bgHover: '#f1f3f4', // Gmail hover color
|
|
9
|
+
bgSelected: '#e8f0fe', // Gmail selected blue tint
|
|
10
|
+
bgUnpaired: '#fef7e0', // Very light orange/yellow for unpaired requests/responses
|
|
11
|
+
|
|
12
|
+
// Text - Gmail style
|
|
13
|
+
textPrimary: '#202124', // Gmail primary text (almost black)
|
|
14
|
+
textSecondary: '#5f6368', // Gmail secondary text (medium gray)
|
|
15
|
+
textTertiary: '#80868b', // Gmail tertiary text (light gray)
|
|
16
|
+
textInverse: '#ffffff', // White text
|
|
17
|
+
|
|
18
|
+
// Borders - Gmail style
|
|
19
|
+
borderLight: '#dadce0', // Gmail light border
|
|
20
|
+
borderMedium: '#bdc1c6', // Gmail medium border
|
|
21
|
+
borderDark: '#9aa0a6', // Gmail dark border
|
|
22
|
+
|
|
23
|
+
// Accents - Gmail/Google Material colors
|
|
24
|
+
accentBlue: '#1a73e8', // Gmail primary blue
|
|
25
|
+
accentBlueHover: '#1557b0', // Darker blue on hover
|
|
26
|
+
accentGreen: '#34a853', // Google green
|
|
27
|
+
accentOrange: '#ea8600', // Google orange
|
|
28
|
+
accentPink: '#d44638', // Gmail red (Jasper)
|
|
29
|
+
accentPurple: '#9334e6', // Google purple
|
|
30
|
+
|
|
31
|
+
// Status colors - Gmail style
|
|
32
|
+
success: '#34a853', // Google green
|
|
33
|
+
warning: '#fbbc04', // Google yellow
|
|
34
|
+
error: '#ea4335', // Google red
|
|
35
|
+
info: '#1a73e8', // Gmail blue
|
|
36
|
+
|
|
37
|
+
// Interactive - Gmail style (lighter, less sharp)
|
|
38
|
+
buttonPrimary: '#4285f4', // Lighter Google blue
|
|
39
|
+
buttonPrimaryHover: '#3367d6',
|
|
40
|
+
buttonSecondary: '#f1f3f4', // Light gray
|
|
41
|
+
buttonSecondaryHover: '#e8eaed',
|
|
42
|
+
buttonDanger: '#ea4335', // Google red (lighter)
|
|
43
|
+
buttonDangerHover: '#d33b2c',
|
|
44
|
+
|
|
45
|
+
// Shadows - Gmail style (subtle)
|
|
46
|
+
shadowSm: 'rgba(60, 64, 67, 0.08)', // Gmail subtle shadow
|
|
47
|
+
shadowMd: 'rgba(60, 64, 67, 0.12)', // Gmail medium shadow
|
|
48
|
+
shadowLg: 'rgba(60, 64, 67, 0.16)', // Gmail larger shadow
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Helper function to add opacity to hex colors
|
|
52
|
+
export function withOpacity(color, opacity) {
|
|
53
|
+
// Remove # if present
|
|
54
|
+
const hex = color.replace('#', '');
|
|
55
|
+
// Convert to RGB
|
|
56
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
57
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
58
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
59
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const fonts = {
|
|
63
|
+
body: "'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
64
|
+
mono: "'Roboto Mono', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
|
|
65
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import anime from 'animejs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Animation utilities for modernizing the UI with anime.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stagger animation for list items
|
|
9
|
+
*/
|
|
10
|
+
export const staggerIn = (selector, options = {}) => {
|
|
11
|
+
return anime({
|
|
12
|
+
targets: selector,
|
|
13
|
+
opacity: [0, 1],
|
|
14
|
+
translateY: [20, 0],
|
|
15
|
+
duration: options.duration || 400,
|
|
16
|
+
delay: anime.stagger(options.delay || 50),
|
|
17
|
+
easing: options.easing || 'easeOutExpo',
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fade in animation
|
|
24
|
+
*/
|
|
25
|
+
export const fadeIn = (selector, options = {}) => {
|
|
26
|
+
return anime({
|
|
27
|
+
targets: selector,
|
|
28
|
+
opacity: [0, 1],
|
|
29
|
+
duration: options.duration || 300,
|
|
30
|
+
easing: options.easing || 'easeOutQuad',
|
|
31
|
+
...options,
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fade out animation
|
|
37
|
+
*/
|
|
38
|
+
export const fadeOut = (selector, options = {}) => {
|
|
39
|
+
return anime({
|
|
40
|
+
targets: selector,
|
|
41
|
+
opacity: [1, 0],
|
|
42
|
+
duration: options.duration || 300,
|
|
43
|
+
easing: options.easing || 'easeInQuad',
|
|
44
|
+
...options,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Slide in from right
|
|
50
|
+
*/
|
|
51
|
+
export const slideInRight = (selector, options = {}) => {
|
|
52
|
+
return anime({
|
|
53
|
+
targets: selector,
|
|
54
|
+
translateX: [options.width || 600, 0],
|
|
55
|
+
opacity: [0, 1],
|
|
56
|
+
duration: options.duration || 400,
|
|
57
|
+
easing: options.easing || 'easeOutExpo',
|
|
58
|
+
...options,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Slide in from left
|
|
64
|
+
*/
|
|
65
|
+
export const slideInLeft = (selector, options = {}) => {
|
|
66
|
+
return anime({
|
|
67
|
+
targets: selector,
|
|
68
|
+
translateX: [-(options.width || 600), 0],
|
|
69
|
+
opacity: [0, 1],
|
|
70
|
+
duration: options.duration || 400,
|
|
71
|
+
easing: options.easing || 'easeOutExpo',
|
|
72
|
+
...options,
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Slide out to right
|
|
78
|
+
*/
|
|
79
|
+
export const slideOutRight = (selector, options = {}) => {
|
|
80
|
+
return anime({
|
|
81
|
+
targets: selector,
|
|
82
|
+
translateX: [0, options.width || 600],
|
|
83
|
+
opacity: [1, 0],
|
|
84
|
+
duration: options.duration || 300,
|
|
85
|
+
easing: options.easing || 'easeInExpo',
|
|
86
|
+
...options,
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Scale in animation
|
|
92
|
+
*/
|
|
93
|
+
export const scaleIn = (selector, options = {}) => {
|
|
94
|
+
return anime({
|
|
95
|
+
targets: selector,
|
|
96
|
+
scale: [0.9, 1],
|
|
97
|
+
opacity: [0, 1],
|
|
98
|
+
duration: options.duration || 300,
|
|
99
|
+
easing: options.easing || 'easeOutBack',
|
|
100
|
+
...options,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Scale out animation
|
|
106
|
+
*/
|
|
107
|
+
export const scaleOut = (selector, options = {}) => {
|
|
108
|
+
return anime({
|
|
109
|
+
targets: selector,
|
|
110
|
+
scale: [1, 0.9],
|
|
111
|
+
opacity: [1, 0],
|
|
112
|
+
duration: options.duration || 200,
|
|
113
|
+
easing: options.easing || 'easeInBack',
|
|
114
|
+
...options,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Tab indicator animation
|
|
120
|
+
*/
|
|
121
|
+
export const animateTabIndicator = (selector, options = {}) => {
|
|
122
|
+
return anime({
|
|
123
|
+
targets: selector,
|
|
124
|
+
translateX: options.translateX || 0,
|
|
125
|
+
width: options.width || 'auto',
|
|
126
|
+
duration: options.duration || 300,
|
|
127
|
+
easing: options.easing || 'easeOutExpo',
|
|
128
|
+
...options,
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Hover scale animation
|
|
134
|
+
*/
|
|
135
|
+
export const hoverScale = (selector, scale = 1.05, options = {}) => {
|
|
136
|
+
return anime({
|
|
137
|
+
targets: selector,
|
|
138
|
+
scale: scale,
|
|
139
|
+
duration: options.duration || 200,
|
|
140
|
+
easing: options.easing || 'easeOutQuad',
|
|
141
|
+
...options,
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Pulse animation
|
|
147
|
+
*/
|
|
148
|
+
export const pulse = (selector, options = {}) => {
|
|
149
|
+
return anime({
|
|
150
|
+
targets: selector,
|
|
151
|
+
scale: [1, 1.1, 1],
|
|
152
|
+
duration: options.duration || 600,
|
|
153
|
+
easing: options.easing || 'easeInOutQuad',
|
|
154
|
+
loop: options.loop || false,
|
|
155
|
+
...options,
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Shake animation
|
|
161
|
+
*/
|
|
162
|
+
export const shake = (selector, options = {}) => {
|
|
163
|
+
return anime({
|
|
164
|
+
targets: selector,
|
|
165
|
+
translateX: [0, -10, 10, -10, 10, 0],
|
|
166
|
+
duration: options.duration || 500,
|
|
167
|
+
easing: options.easing || 'easeInOutQuad',
|
|
168
|
+
...options,
|
|
169
|
+
});
|
|
170
|
+
};
|