@mcp-shark/mcp-shark 1.5.7 → 1.5.9
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 +3 -2
- package/core/configs/environment.js +10 -7
- package/core/configs/index.js +0 -1
- package/core/constants/Defaults.js +2 -0
- package/core/libraries/TransportLibrary.js +72 -0
- package/core/libraries/index.js +1 -0
- package/core/mcp-server/index.js +22 -6
- package/core/mcp-server/server/external/single/transport.js +2 -2
- package/core/services/ConfigDetectionService.js +35 -22
- package/core/services/McpDiscoveryService.js +1 -1
- package/core/services/ServerManagementService.js +25 -4
- package/package.json +2 -2
- package/ui/dist/assets/index-D1MNKhKd.js +44 -0
- package/ui/dist/index.html +1 -1
- package/ui/server/controllers/ServerManagementController.js +3 -0
- package/ui/src/App.jsx +13 -5
- package/ui/src/ShutdownPage.jsx +91 -0
- package/ui/src/components/App/ActionMenu.jsx +179 -0
- package/ui/src/components/App/ApiDocsButton.jsx +56 -35
- package/ui/src/components/App/HelpButton.jsx +57 -35
- package/ui/src/components/App/ShutdownButton.jsx +142 -0
- package/ui/src/components/ShuttingDownModal.jsx +100 -0
- package/core/configs/codex.js +0 -68
- package/ui/dist/assets/index-CArYxKxS.js +0 -35
- package/ui/server/routes/smartscan/transport.js +0 -53
package/ui/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<title>MCP Shark</title>
|
|
8
8
|
<link rel="icon" type="image/png" href="/og-image.png" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/og-image.png" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-D1MNKhKd.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-Cc-IUa83.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
@@ -39,6 +39,9 @@ export class ServerManagementController {
|
|
|
39
39
|
fileContent,
|
|
40
40
|
selectedServices,
|
|
41
41
|
port: 9851,
|
|
42
|
+
onLog: (type, message) => {
|
|
43
|
+
this._addLogEntry(type, message);
|
|
44
|
+
},
|
|
42
45
|
onError: (err) => {
|
|
43
46
|
this._addLogEntry('error', `Failed to start mcp-shark server: ${err.message}`);
|
|
44
47
|
throw err;
|
package/ui/src/App.jsx
CHANGED
|
@@ -2,10 +2,10 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
import CompositeLogs from './CompositeLogs';
|
|
3
3
|
import CompositeSetup from './CompositeSetup';
|
|
4
4
|
import IntroTour from './IntroTour';
|
|
5
|
+
import ShutdownPage from './ShutdownPage';
|
|
5
6
|
import SmartScan from './SmartScan';
|
|
6
7
|
import TabNavigation from './TabNavigation';
|
|
7
|
-
import
|
|
8
|
-
import HelpButton from './components/App/HelpButton';
|
|
8
|
+
import ActionMenu from './components/App/ActionMenu';
|
|
9
9
|
import TrafficTab from './components/App/TrafficTab';
|
|
10
10
|
import { useAppState } from './components/App/useAppState';
|
|
11
11
|
import McpPlayground from './components/McpPlayground';
|
|
@@ -84,9 +84,8 @@ function App() {
|
|
|
84
84
|
<div style={{ position: 'relative' }} data-tour="tabs">
|
|
85
85
|
<TabNavigation activeTab={activeTab} onTabChange={setActiveTab} />
|
|
86
86
|
</div>
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
onClick={() => {
|
|
87
|
+
<ActionMenu
|
|
88
|
+
onHelpClick={() => {
|
|
90
89
|
if (showTour) {
|
|
91
90
|
setShowTour(false);
|
|
92
91
|
setTourKey((prev) => prev + 1);
|
|
@@ -148,6 +147,15 @@ function App() {
|
|
|
148
147
|
<SmartScan />
|
|
149
148
|
</div>
|
|
150
149
|
)}
|
|
150
|
+
|
|
151
|
+
{activeTab === 'shutdown' && (
|
|
152
|
+
<div
|
|
153
|
+
data-tab-content
|
|
154
|
+
style={{ flex: 1, overflow: 'hidden', width: '100%', height: '100%' }}
|
|
155
|
+
>
|
|
156
|
+
<ShutdownPage />
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
151
159
|
</div>
|
|
152
160
|
);
|
|
153
161
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { colors, fonts } from './theme';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shutdown page - shows message and redirects to root after 3 seconds
|
|
6
|
+
*/
|
|
7
|
+
function ShutdownPage() {
|
|
8
|
+
const [countdown, setCountdown] = useState(3);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const interval = setInterval(() => {
|
|
12
|
+
setCountdown((prev) => {
|
|
13
|
+
if (prev <= 1) {
|
|
14
|
+
clearInterval(interval);
|
|
15
|
+
window.location.href = '/';
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
return prev - 1;
|
|
19
|
+
});
|
|
20
|
+
}, 1000);
|
|
21
|
+
|
|
22
|
+
return () => clearInterval(interval);
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
style={{
|
|
28
|
+
display: 'flex',
|
|
29
|
+
flexDirection: 'column',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
justifyContent: 'center',
|
|
32
|
+
height: '100vh',
|
|
33
|
+
background: colors.bgPrimary,
|
|
34
|
+
fontFamily: fonts.body,
|
|
35
|
+
padding: '20px',
|
|
36
|
+
textAlign: 'center',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<div
|
|
40
|
+
style={{
|
|
41
|
+
maxWidth: '500px',
|
|
42
|
+
width: '100%',
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<h1
|
|
46
|
+
style={{
|
|
47
|
+
fontSize: '32px',
|
|
48
|
+
fontWeight: '600',
|
|
49
|
+
color: colors.textPrimary,
|
|
50
|
+
margin: '0 0 16px 0',
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
MCP Shark Shutdown
|
|
54
|
+
</h1>
|
|
55
|
+
<p
|
|
56
|
+
style={{
|
|
57
|
+
fontSize: '16px',
|
|
58
|
+
color: colors.textSecondary,
|
|
59
|
+
margin: '0 0 32px 0',
|
|
60
|
+
lineHeight: '1.6',
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
MCP Shark has been shut down.
|
|
64
|
+
</p>
|
|
65
|
+
<div
|
|
66
|
+
style={{
|
|
67
|
+
padding: '24px',
|
|
68
|
+
background: colors.bgCard,
|
|
69
|
+
borderRadius: '12px',
|
|
70
|
+
border: `1px solid ${colors.borderLight}`,
|
|
71
|
+
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<p
|
|
75
|
+
style={{
|
|
76
|
+
fontSize: '14px',
|
|
77
|
+
color: colors.textSecondary,
|
|
78
|
+
margin: '0 0 16px 0',
|
|
79
|
+
lineHeight: '1.6',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
The MCP Shark server has been stopped. Redirecting in {countdown} second
|
|
83
|
+
{countdown !== 1 ? 's' : ''}...
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default ShutdownPage;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { IconMenu2, IconX } from '@tabler/icons-react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { colors, fonts } from '../../theme';
|
|
4
|
+
import ApiDocsButton from './ApiDocsButton';
|
|
5
|
+
import HelpButton from './HelpButton';
|
|
6
|
+
import ShutdownButton from './ShutdownButton';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Expandable action menu grouping API, Help, and Shutdown buttons
|
|
10
|
+
*/
|
|
11
|
+
export default function ActionMenu({ onHelpClick }) {
|
|
12
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
13
|
+
const menuRef = useRef(null);
|
|
14
|
+
|
|
15
|
+
// Close menu when clicking outside
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const handleClickOutside = (event) => {
|
|
18
|
+
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
19
|
+
setIsExpanded(false);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (isExpanded) {
|
|
24
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
25
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
26
|
+
}
|
|
27
|
+
}, [isExpanded]);
|
|
28
|
+
|
|
29
|
+
const buttonStyle = {
|
|
30
|
+
position: 'fixed',
|
|
31
|
+
bottom: '20px',
|
|
32
|
+
right: '20px',
|
|
33
|
+
background: colors.bgCard,
|
|
34
|
+
border: `1px solid ${colors.borderLight}`,
|
|
35
|
+
borderRadius: '50%',
|
|
36
|
+
width: '48px',
|
|
37
|
+
height: '48px',
|
|
38
|
+
padding: 0,
|
|
39
|
+
color: colors.textSecondary,
|
|
40
|
+
cursor: 'pointer',
|
|
41
|
+
fontFamily: fonts.body,
|
|
42
|
+
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
43
|
+
display: 'flex',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
justifyContent: 'center',
|
|
46
|
+
zIndex: 9999,
|
|
47
|
+
transition: 'all 0.2s ease',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const menuItemStyle = {
|
|
51
|
+
position: 'absolute',
|
|
52
|
+
right: '0',
|
|
53
|
+
background: colors.bgCard,
|
|
54
|
+
border: `1px solid ${colors.borderLight}`,
|
|
55
|
+
borderRadius: '50%',
|
|
56
|
+
width: '48px',
|
|
57
|
+
height: '48px',
|
|
58
|
+
padding: 0,
|
|
59
|
+
color: colors.textSecondary,
|
|
60
|
+
cursor: 'pointer',
|
|
61
|
+
fontFamily: fonts.body,
|
|
62
|
+
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
justifyContent: 'center',
|
|
66
|
+
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
67
|
+
opacity: isExpanded ? 1 : 0,
|
|
68
|
+
pointerEvents: isExpanded ? 'auto' : 'none',
|
|
69
|
+
transform: isExpanded ? 'scale(1) translateY(0)' : 'scale(0.8) translateY(10px)',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div ref={menuRef} style={{ position: 'fixed', bottom: '20px', right: '20px', zIndex: 9999 }}>
|
|
74
|
+
{/* API Docs Button - Top */}
|
|
75
|
+
<ApiDocsButton
|
|
76
|
+
style={{
|
|
77
|
+
...menuItemStyle,
|
|
78
|
+
bottom: isExpanded ? '180px' : '0',
|
|
79
|
+
position: 'absolute',
|
|
80
|
+
left: 'auto', // Ensure no left positioning conflicts
|
|
81
|
+
}}
|
|
82
|
+
onClick={() => setIsExpanded(false)}
|
|
83
|
+
onMouseEnter={(e) => {
|
|
84
|
+
if (isExpanded) {
|
|
85
|
+
e.currentTarget.style.transform = 'scale(1.1) translateY(0)';
|
|
86
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
87
|
+
}
|
|
88
|
+
}}
|
|
89
|
+
onMouseLeave={(e) => {
|
|
90
|
+
if (isExpanded) {
|
|
91
|
+
e.currentTarget.style.transform = 'scale(1) translateY(0)';
|
|
92
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* Help Button - Middle */}
|
|
98
|
+
<HelpButton
|
|
99
|
+
onClick={() => {
|
|
100
|
+
onHelpClick();
|
|
101
|
+
setIsExpanded(false);
|
|
102
|
+
}}
|
|
103
|
+
style={{
|
|
104
|
+
...menuItemStyle,
|
|
105
|
+
bottom: isExpanded ? '120px' : '0',
|
|
106
|
+
position: 'absolute',
|
|
107
|
+
left: 'auto', // Ensure no left positioning conflicts
|
|
108
|
+
}}
|
|
109
|
+
onMouseEnter={(e) => {
|
|
110
|
+
if (isExpanded) {
|
|
111
|
+
e.currentTarget.style.transform = 'scale(1.1) translateY(0)';
|
|
112
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
115
|
+
onMouseLeave={(e) => {
|
|
116
|
+
if (isExpanded) {
|
|
117
|
+
e.currentTarget.style.transform = 'scale(1) translateY(0)';
|
|
118
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
119
|
+
}
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
{/* Shutdown Button - Bottom (closest to main button) */}
|
|
124
|
+
<ShutdownButton
|
|
125
|
+
style={{
|
|
126
|
+
...menuItemStyle,
|
|
127
|
+
bottom: isExpanded ? '60px' : '0',
|
|
128
|
+
position: 'absolute',
|
|
129
|
+
left: 'auto', // Override default left positioning
|
|
130
|
+
}}
|
|
131
|
+
onClick={() => setIsExpanded(false)}
|
|
132
|
+
onMouseEnter={(e) => {
|
|
133
|
+
if (isExpanded) {
|
|
134
|
+
e.currentTarget.style.transform = 'scale(1.1) translateY(0)';
|
|
135
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
138
|
+
onMouseLeave={(e) => {
|
|
139
|
+
if (isExpanded) {
|
|
140
|
+
e.currentTarget.style.transform = 'scale(1) translateY(0)';
|
|
141
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
142
|
+
}
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
{/* Main Toggle Button */}
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
150
|
+
style={{
|
|
151
|
+
...buttonStyle,
|
|
152
|
+
background: isExpanded ? colors.accentBlue : colors.bgCard,
|
|
153
|
+
color: isExpanded ? colors.textInverse : colors.textSecondary,
|
|
154
|
+
}}
|
|
155
|
+
onMouseEnter={(e) => {
|
|
156
|
+
if (!isExpanded) {
|
|
157
|
+
e.currentTarget.style.background = colors.bgHover;
|
|
158
|
+
e.currentTarget.style.color = colors.textPrimary;
|
|
159
|
+
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
160
|
+
e.currentTarget.style.transform = 'scale(1.1)';
|
|
161
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
onMouseLeave={(e) => {
|
|
165
|
+
if (!isExpanded) {
|
|
166
|
+
e.currentTarget.style.background = colors.bgCard;
|
|
167
|
+
e.currentTarget.style.color = colors.textSecondary;
|
|
168
|
+
e.currentTarget.style.borderColor = colors.borderLight;
|
|
169
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
170
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
171
|
+
}
|
|
172
|
+
}}
|
|
173
|
+
title={isExpanded ? 'Close menu' : 'Open menu'}
|
|
174
|
+
>
|
|
175
|
+
{isExpanded ? <IconX size={20} stroke={1.5} /> : <IconMenu2 size={20} stroke={1.5} />}
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -4,10 +4,62 @@ import { colors, fonts } from '../../theme';
|
|
|
4
4
|
/**
|
|
5
5
|
* API Documentation button component
|
|
6
6
|
* Opens Swagger/OpenAPI documentation in a new tab
|
|
7
|
+
* @param {Object} props
|
|
8
|
+
* @param {Object} props.style - Custom styles for the button
|
|
9
|
+
* @param {Function} props.onClick - Optional callback when button is clicked
|
|
10
|
+
* @param {Function} props.onMouseEnter - Optional mouse enter handler
|
|
11
|
+
* @param {Function} props.onMouseLeave - Optional mouse leave handler
|
|
7
12
|
*/
|
|
8
|
-
export default function ApiDocsButton() {
|
|
13
|
+
export default function ApiDocsButton({ style, onClick, onMouseEnter, onMouseLeave }) {
|
|
9
14
|
const handleClick = () => {
|
|
10
15
|
window.open('/api-docs', '_blank');
|
|
16
|
+
if (onClick) {
|
|
17
|
+
onClick();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const defaultStyle = {
|
|
22
|
+
position: 'fixed',
|
|
23
|
+
bottom: '20px',
|
|
24
|
+
right: '80px',
|
|
25
|
+
background: colors.bgCard,
|
|
26
|
+
border: `1px solid ${colors.borderLight}`,
|
|
27
|
+
borderRadius: '50%',
|
|
28
|
+
width: '48px',
|
|
29
|
+
height: '48px',
|
|
30
|
+
padding: 0,
|
|
31
|
+
color: colors.textSecondary,
|
|
32
|
+
cursor: 'pointer',
|
|
33
|
+
fontFamily: fonts.body,
|
|
34
|
+
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
35
|
+
display: 'flex',
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
zIndex: 9999,
|
|
39
|
+
transition: 'all 0.2s ease',
|
|
40
|
+
...style,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleMouseEnter = (e) => {
|
|
44
|
+
e.currentTarget.style.background = colors.bgHover;
|
|
45
|
+
e.currentTarget.style.color = colors.textPrimary;
|
|
46
|
+
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
47
|
+
e.currentTarget.style.transform = 'scale(1.1)';
|
|
48
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
49
|
+
if (onMouseEnter) {
|
|
50
|
+
onMouseEnter(e);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleMouseLeave = (e) => {
|
|
55
|
+
e.currentTarget.style.background = colors.bgCard;
|
|
56
|
+
e.currentTarget.style.color = colors.textSecondary;
|
|
57
|
+
e.currentTarget.style.borderColor = colors.borderLight;
|
|
58
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
59
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
60
|
+
if (onMouseLeave) {
|
|
61
|
+
onMouseLeave(e);
|
|
62
|
+
}
|
|
11
63
|
};
|
|
12
64
|
|
|
13
65
|
return (
|
|
@@ -15,40 +67,9 @@ export default function ApiDocsButton() {
|
|
|
15
67
|
type="button"
|
|
16
68
|
onClick={handleClick}
|
|
17
69
|
data-tour="api-docs-button"
|
|
18
|
-
style={
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
right: '80px',
|
|
22
|
-
background: colors.bgCard,
|
|
23
|
-
border: `1px solid ${colors.borderLight}`,
|
|
24
|
-
borderRadius: '50%',
|
|
25
|
-
width: '48px',
|
|
26
|
-
height: '48px',
|
|
27
|
-
padding: 0,
|
|
28
|
-
color: colors.textSecondary,
|
|
29
|
-
cursor: 'pointer',
|
|
30
|
-
fontFamily: fonts.body,
|
|
31
|
-
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
32
|
-
display: 'flex',
|
|
33
|
-
alignItems: 'center',
|
|
34
|
-
justifyContent: 'center',
|
|
35
|
-
zIndex: 9999,
|
|
36
|
-
transition: 'all 0.2s ease',
|
|
37
|
-
}}
|
|
38
|
-
onMouseEnter={(e) => {
|
|
39
|
-
e.currentTarget.style.background = colors.bgHover;
|
|
40
|
-
e.currentTarget.style.color = colors.textPrimary;
|
|
41
|
-
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
42
|
-
e.currentTarget.style.transform = 'scale(1.1)';
|
|
43
|
-
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
44
|
-
}}
|
|
45
|
-
onMouseLeave={(e) => {
|
|
46
|
-
e.currentTarget.style.background = colors.bgCard;
|
|
47
|
-
e.currentTarget.style.color = colors.textSecondary;
|
|
48
|
-
e.currentTarget.style.borderColor = colors.borderLight;
|
|
49
|
-
e.currentTarget.style.transform = 'scale(1)';
|
|
50
|
-
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
51
|
-
}}
|
|
70
|
+
style={defaultStyle}
|
|
71
|
+
onMouseEnter={handleMouseEnter}
|
|
72
|
+
onMouseLeave={handleMouseLeave}
|
|
52
73
|
title="View API Documentation"
|
|
53
74
|
>
|
|
54
75
|
<IconApi size={20} stroke={1.5} />
|
|
@@ -20,46 +20,68 @@ const TourIcon = ({ size = 16, color = 'currentColor' }) => (
|
|
|
20
20
|
</svg>
|
|
21
21
|
);
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Help button component
|
|
25
|
+
* Starts the application tour
|
|
26
|
+
* @param {Object} props
|
|
27
|
+
* @param {Function} props.onClick - Callback when button is clicked
|
|
28
|
+
* @param {Object} props.style - Custom styles for the button
|
|
29
|
+
* @param {Function} props.onMouseEnter - Optional mouse enter handler
|
|
30
|
+
* @param {Function} props.onMouseLeave - Optional mouse leave handler
|
|
31
|
+
*/
|
|
32
|
+
export default function HelpButton({ onClick, style, onMouseEnter, onMouseLeave }) {
|
|
33
|
+
const defaultStyle = {
|
|
34
|
+
position: 'fixed',
|
|
35
|
+
bottom: '20px',
|
|
36
|
+
right: '20px',
|
|
37
|
+
background: colors.bgCard,
|
|
38
|
+
border: `1px solid ${colors.borderLight}`,
|
|
39
|
+
borderRadius: '50%',
|
|
40
|
+
width: '48px',
|
|
41
|
+
height: '48px',
|
|
42
|
+
padding: 0,
|
|
43
|
+
color: colors.textSecondary,
|
|
44
|
+
cursor: 'pointer',
|
|
45
|
+
fontFamily: fonts.body,
|
|
46
|
+
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
47
|
+
display: 'flex',
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
zIndex: 9999,
|
|
51
|
+
transition: 'all 0.2s ease',
|
|
52
|
+
...style,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleMouseEnter = (e) => {
|
|
56
|
+
e.currentTarget.style.background = colors.bgHover;
|
|
57
|
+
e.currentTarget.style.color = colors.textPrimary;
|
|
58
|
+
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
59
|
+
e.currentTarget.style.transform = 'scale(1.1)';
|
|
60
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
61
|
+
if (onMouseEnter) {
|
|
62
|
+
onMouseEnter(e);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleMouseLeave = (e) => {
|
|
67
|
+
e.currentTarget.style.background = colors.bgCard;
|
|
68
|
+
e.currentTarget.style.color = colors.textSecondary;
|
|
69
|
+
e.currentTarget.style.borderColor = colors.borderLight;
|
|
70
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
71
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
72
|
+
if (onMouseLeave) {
|
|
73
|
+
onMouseLeave(e);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
24
77
|
return (
|
|
25
78
|
<button
|
|
26
79
|
type="button"
|
|
27
80
|
onClick={onClick}
|
|
28
81
|
data-tour="help-button"
|
|
29
|
-
style={
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
right: '20px',
|
|
33
|
-
background: colors.bgCard,
|
|
34
|
-
border: `1px solid ${colors.borderLight}`,
|
|
35
|
-
borderRadius: '50%',
|
|
36
|
-
width: '48px',
|
|
37
|
-
height: '48px',
|
|
38
|
-
padding: 0,
|
|
39
|
-
color: colors.textSecondary,
|
|
40
|
-
cursor: 'pointer',
|
|
41
|
-
fontFamily: fonts.body,
|
|
42
|
-
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
43
|
-
display: 'flex',
|
|
44
|
-
alignItems: 'center',
|
|
45
|
-
justifyContent: 'center',
|
|
46
|
-
zIndex: 9999,
|
|
47
|
-
transition: 'all 0.2s ease',
|
|
48
|
-
}}
|
|
49
|
-
onMouseEnter={(e) => {
|
|
50
|
-
e.currentTarget.style.background = colors.bgHover;
|
|
51
|
-
e.currentTarget.style.color = colors.textPrimary;
|
|
52
|
-
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
53
|
-
e.currentTarget.style.transform = 'scale(1.1)';
|
|
54
|
-
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
55
|
-
}}
|
|
56
|
-
onMouseLeave={(e) => {
|
|
57
|
-
e.currentTarget.style.background = colors.bgCard;
|
|
58
|
-
e.currentTarget.style.color = colors.textSecondary;
|
|
59
|
-
e.currentTarget.style.borderColor = colors.borderLight;
|
|
60
|
-
e.currentTarget.style.transform = 'scale(1)';
|
|
61
|
-
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
62
|
-
}}
|
|
82
|
+
style={defaultStyle}
|
|
83
|
+
onMouseEnter={handleMouseEnter}
|
|
84
|
+
onMouseLeave={handleMouseLeave}
|
|
63
85
|
title="Start tour"
|
|
64
86
|
>
|
|
65
87
|
<TourIcon size={20} />
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { IconPower } from '@tabler/icons-react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { colors, fonts } from '../../theme';
|
|
4
|
+
import AlertModal from '../AlertModal';
|
|
5
|
+
import ConfirmationModal from '../ConfirmationModal';
|
|
6
|
+
import ShuttingDownModal from '../ShuttingDownModal';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shutdown button component
|
|
10
|
+
* Shuts down the MCP Shark application
|
|
11
|
+
* @param {Object} props
|
|
12
|
+
* @param {Object} props.style - Custom styles for the button
|
|
13
|
+
* @param {Function} props.onClick - Optional callback when button is clicked
|
|
14
|
+
* @param {Function} props.onMouseEnter - Optional mouse enter handler
|
|
15
|
+
* @param {Function} props.onMouseLeave - Optional mouse leave handler
|
|
16
|
+
*/
|
|
17
|
+
export default function ShutdownButton({ style, onClick, onMouseEnter, onMouseLeave }) {
|
|
18
|
+
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
|
19
|
+
const [showErrorModal, setShowErrorModal] = useState(false);
|
|
20
|
+
const [showShuttingDownModal, setShowShuttingDownModal] = useState(false);
|
|
21
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
22
|
+
|
|
23
|
+
const handleShutdown = async () => {
|
|
24
|
+
setShowShuttingDownModal(true);
|
|
25
|
+
|
|
26
|
+
const { success, message } = await shutdownOrTimeout();
|
|
27
|
+
if (success) {
|
|
28
|
+
console.log('Shutdown successful');
|
|
29
|
+
window.location.reload(); // reload the page to reset the app
|
|
30
|
+
} else {
|
|
31
|
+
setShowShuttingDownModal(false);
|
|
32
|
+
setErrorMessage(message);
|
|
33
|
+
setShowErrorModal(true);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleClick = () => {
|
|
38
|
+
setShowConfirmModal(true);
|
|
39
|
+
if (onClick) {
|
|
40
|
+
onClick();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const defaultStyle = {
|
|
45
|
+
position: 'fixed',
|
|
46
|
+
bottom: '20px',
|
|
47
|
+
left: '20px',
|
|
48
|
+
background: colors.bgCard,
|
|
49
|
+
border: `1px solid ${colors.borderLight}`,
|
|
50
|
+
borderRadius: '50%',
|
|
51
|
+
width: '48px',
|
|
52
|
+
height: '48px',
|
|
53
|
+
padding: 0,
|
|
54
|
+
color: colors.textSecondary,
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
fontFamily: fonts.body,
|
|
57
|
+
boxShadow: `0 4px 12px ${colors.shadowMd}`,
|
|
58
|
+
display: 'flex',
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
justifyContent: 'center',
|
|
61
|
+
zIndex: 9999,
|
|
62
|
+
transition: 'all 0.2s ease',
|
|
63
|
+
...style,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleMouseEnter = (e) => {
|
|
67
|
+
e.currentTarget.style.background = colors.bgHover;
|
|
68
|
+
e.currentTarget.style.color = colors.error;
|
|
69
|
+
e.currentTarget.style.borderColor = colors.borderMedium;
|
|
70
|
+
e.currentTarget.style.transform = 'scale(1.1)';
|
|
71
|
+
e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
|
|
72
|
+
if (onMouseEnter) {
|
|
73
|
+
onMouseEnter(e);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleMouseLeave = (e) => {
|
|
78
|
+
e.currentTarget.style.background = colors.bgCard;
|
|
79
|
+
e.currentTarget.style.color = colors.textSecondary;
|
|
80
|
+
e.currentTarget.style.borderColor = colors.borderLight;
|
|
81
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
82
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
|
|
83
|
+
if (onMouseLeave) {
|
|
84
|
+
onMouseLeave(e);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={handleClick}
|
|
93
|
+
data-tour="shutdown-button"
|
|
94
|
+
style={defaultStyle}
|
|
95
|
+
onMouseEnter={handleMouseEnter}
|
|
96
|
+
onMouseLeave={handleMouseLeave}
|
|
97
|
+
title="Shutdown MCP Shark"
|
|
98
|
+
>
|
|
99
|
+
<IconPower size={20} stroke={1.5} />
|
|
100
|
+
</button>
|
|
101
|
+
<ConfirmationModal
|
|
102
|
+
isOpen={showConfirmModal}
|
|
103
|
+
onClose={() => setShowConfirmModal(false)}
|
|
104
|
+
onConfirm={handleShutdown}
|
|
105
|
+
title="Shutdown MCP Shark"
|
|
106
|
+
message="Are you sure you want to shutdown MCP Shark? This will stop the server and all services."
|
|
107
|
+
confirmText="Shutdown"
|
|
108
|
+
cancelText="Cancel"
|
|
109
|
+
danger={true}
|
|
110
|
+
/>
|
|
111
|
+
<AlertModal
|
|
112
|
+
isOpen={showErrorModal}
|
|
113
|
+
onClose={() => setShowErrorModal(false)}
|
|
114
|
+
title="Shutdown Failed"
|
|
115
|
+
message={errorMessage}
|
|
116
|
+
type="error"
|
|
117
|
+
/>
|
|
118
|
+
<ShuttingDownModal isOpen={showShuttingDownModal} />
|
|
119
|
+
</>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function shutdownOrTimeout() {
|
|
124
|
+
return Promise.race([callShutdown(), timeout(3000)]);
|
|
125
|
+
}
|
|
126
|
+
async function timeout(ms) {
|
|
127
|
+
return new Promise((resolve) =>
|
|
128
|
+
setTimeout(() => resolve({ success: true, message: 'Shutdown timed out' }), ms)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function callShutdown() {
|
|
133
|
+
try {
|
|
134
|
+
const response = await fetch('/api/composite/shutdown', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
});
|
|
137
|
+
return { success: response.ok, message: response.message };
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Shutdown error:', error);
|
|
140
|
+
return { success: false, message: error.message || 'Failed to shutdown server' };
|
|
141
|
+
}
|
|
142
|
+
}
|