@mcp-shark/mcp-shark 1.4.2 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -645
- package/bin/mcp-shark.js +30 -36
- package/mcp-server/index.js +115 -0
- package/mcp-server/lib/auditor/audit.js +22 -38
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +4 -12
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
- package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
- package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
- package/mcp-server/lib/server/internal/run.js +4 -16
- package/mcp-server/lib/server/internal/server.js +6 -7
- package/mcp-server/lib/server/internal/session.js +2 -15
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/shared/logger.js +90 -0
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +45 -47
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +7 -6
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +86 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -4
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
- package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
- package/ui/src/components/McpPlayground.jsx +5 -2
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +32 -101
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -260
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileSync,
|
|
1
|
+
import { readFileSync, readdirSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import logger from '../logger.js';
|
|
3
4
|
import { ensureScanResultsDirectory } from './directory.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -30,7 +31,7 @@ export function getCachedScanResultsForServer(serverName) {
|
|
|
30
31
|
}
|
|
31
32
|
} catch (error) {
|
|
32
33
|
// Skip files that can't be parsed
|
|
33
|
-
|
|
34
|
+
logger.warn({ file, error: error.message }, 'Error reading scan result file');
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -39,7 +40,7 @@ export function getCachedScanResultsForServer(serverName) {
|
|
|
39
40
|
|
|
40
41
|
return results;
|
|
41
42
|
} catch (error) {
|
|
42
|
-
|
|
43
|
+
logger.error({ error: error.message }, 'Error getting cached scan results for server');
|
|
43
44
|
return [];
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -54,9 +55,8 @@ export function clearOldScanResults(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
|
|
|
54
55
|
const scanResultsDir = ensureScanResultsDirectory();
|
|
55
56
|
const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
|
|
56
57
|
const cutoffTime = Date.now() - maxAgeMs;
|
|
57
|
-
let deletedCount = 0;
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
const deletedCount = files.reduce((count, file) => {
|
|
60
60
|
try {
|
|
61
61
|
const filePath = join(scanResultsDir, file);
|
|
62
62
|
const fileContent = readFileSync(filePath, 'utf8');
|
|
@@ -64,17 +64,22 @@ export function clearOldScanResults(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
|
|
|
64
64
|
|
|
65
65
|
if (data.updatedAt && data.updatedAt < cutoffTime) {
|
|
66
66
|
unlinkSync(filePath);
|
|
67
|
-
|
|
67
|
+
return count + 1;
|
|
68
68
|
}
|
|
69
|
+
return count;
|
|
69
70
|
} catch (error) {
|
|
70
71
|
// Skip files that can't be parsed
|
|
71
|
-
|
|
72
|
+
logger.warn(
|
|
73
|
+
{ file, error: error.message },
|
|
74
|
+
'Error processing scan result file for cleanup'
|
|
75
|
+
);
|
|
76
|
+
return count;
|
|
72
77
|
}
|
|
73
|
-
}
|
|
78
|
+
}, 0);
|
|
74
79
|
|
|
75
80
|
return deletedCount;
|
|
76
81
|
} catch (error) {
|
|
77
|
-
|
|
82
|
+
logger.error({ error: error.message }, 'Error clearing old scan results');
|
|
78
83
|
return 0;
|
|
79
84
|
}
|
|
80
85
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export function serializeBigInt(obj) {
|
|
2
|
-
if (obj === null || obj === undefined)
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
if (obj === null || obj === undefined) {
|
|
3
|
+
return obj;
|
|
4
|
+
}
|
|
5
|
+
if (typeof obj === 'bigint') {
|
|
6
|
+
return obj.toString();
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(obj)) {
|
|
9
|
+
return obj.map(serializeBigInt);
|
|
10
|
+
}
|
|
5
11
|
if (typeof obj === 'object') {
|
|
6
12
|
const result = {};
|
|
7
13
|
for (const [key, value] of Object.entries(obj)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { getWorkingDirectory, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
|
|
3
2
|
import { join } from 'node:path';
|
|
3
|
+
import { getWorkingDirectory, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
|
|
4
|
+
import logger from './logger.js';
|
|
4
5
|
|
|
5
6
|
const SMART_SCAN_TOKEN_NAME = 'smart-scan-token.json';
|
|
6
7
|
|
|
@@ -18,7 +19,7 @@ export function readSmartScanToken() {
|
|
|
18
19
|
}
|
|
19
20
|
return null;
|
|
20
21
|
} catch (error) {
|
|
21
|
-
|
|
22
|
+
logger.error({ error: error.message }, 'Error reading Smart Scan token');
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -36,7 +37,7 @@ export function writeSmartScanToken(token) {
|
|
|
36
37
|
writeFileSync(tokenPath, JSON.stringify(data, null, 2), { mode: 0o600 }); // Read/write for owner only
|
|
37
38
|
return true;
|
|
38
39
|
} catch (error) {
|
|
39
|
-
|
|
40
|
+
logger.error({ error: error.message }, 'Error writing Smart Scan token');
|
|
40
41
|
return false;
|
|
41
42
|
}
|
|
42
43
|
}
|
package/ui/server.js
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
1
4
|
import express from 'express';
|
|
2
|
-
import { createServer } from 'http';
|
|
3
5
|
import { WebSocketServer } from 'ws';
|
|
4
|
-
import * as path from 'node:path';
|
|
5
|
-
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
6
|
|
|
7
|
+
import { getDatabaseFile, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
|
|
7
8
|
import { openDb } from 'mcp-shark-common/db/init.js';
|
|
8
|
-
import {
|
|
9
|
-
getDatabaseFile,
|
|
10
|
-
prepareAppDataSpaces,
|
|
11
|
-
getMcpConfigPath,
|
|
12
|
-
} from 'mcp-shark-common/configs/index.js';
|
|
13
9
|
import { queryRequests } from 'mcp-shark-common/db/query.js';
|
|
14
10
|
import { restoreOriginalConfig } from './server/utils/config.js';
|
|
15
11
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { createSessionsRoutes } from './server/routes/sessions.js';
|
|
19
|
-
import { createStatisticsRoutes } from './server/routes/statistics.js';
|
|
20
|
-
import { createLogsRoutes } from './server/routes/logs.js';
|
|
12
|
+
import { createBackupRoutes } from './server/routes/backups/index.js';
|
|
13
|
+
import { createCompositeRoutes } from './server/routes/composite/index.js';
|
|
21
14
|
import { createConfigRoutes } from './server/routes/config.js';
|
|
22
|
-
import {
|
|
23
|
-
import { createCompositeRoutes } from './server/routes/composite.js';
|
|
15
|
+
import { createConversationsRoutes } from './server/routes/conversations.js';
|
|
24
16
|
import { createHelpRoutes } from './server/routes/help.js';
|
|
17
|
+
import { createLogsRoutes } from './server/routes/logs.js';
|
|
25
18
|
import { createPlaygroundRoutes } from './server/routes/playground.js';
|
|
19
|
+
import { createRequestsRoutes } from './server/routes/requests.js';
|
|
20
|
+
import { createSessionsRoutes } from './server/routes/sessions.js';
|
|
21
|
+
import { createSettingsRoutes } from './server/routes/settings.js';
|
|
26
22
|
import { createSmartScanRoutes } from './server/routes/smartscan.js';
|
|
23
|
+
import { createStatisticsRoutes } from './server/routes/statistics.js';
|
|
24
|
+
import { serializeBigInt } from './server/utils/serialization.js';
|
|
27
25
|
|
|
28
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
27
|
const __dirname = path.dirname(__filename);
|
|
30
28
|
|
|
31
|
-
const
|
|
29
|
+
const _MAX_LOG_LINES = 10000;
|
|
32
30
|
|
|
33
31
|
export function createUIServer() {
|
|
34
32
|
prepareAppDataSpaces();
|
|
@@ -42,10 +40,10 @@ export function createUIServer() {
|
|
|
42
40
|
|
|
43
41
|
const clients = new Set();
|
|
44
42
|
const mcpSharkLogs = [];
|
|
45
|
-
const processState = {
|
|
43
|
+
const processState = { mcpSharkServer: null };
|
|
46
44
|
|
|
47
|
-
const setMcpSharkProcess = (
|
|
48
|
-
processState.
|
|
45
|
+
const setMcpSharkProcess = (server) => {
|
|
46
|
+
processState.mcpSharkServer = server;
|
|
49
47
|
};
|
|
50
48
|
|
|
51
49
|
wss.on('connection', (ws) => {
|
|
@@ -73,7 +71,7 @@ export function createUIServer() {
|
|
|
73
71
|
const logsRoutes = createLogsRoutes(mcpSharkLogs, broadcastLogUpdate);
|
|
74
72
|
const configRoutes = createConfigRoutes();
|
|
75
73
|
const backupRoutes = createBackupRoutes();
|
|
76
|
-
const getMcpSharkProcess = () => processState.
|
|
74
|
+
const getMcpSharkProcess = () => processState.mcpSharkServer;
|
|
77
75
|
const compositeRoutes = createCompositeRoutes(
|
|
78
76
|
getMcpSharkProcess,
|
|
79
77
|
setMcpSharkProcess,
|
|
@@ -83,6 +81,7 @@ export function createUIServer() {
|
|
|
83
81
|
const helpRoutes = createHelpRoutes();
|
|
84
82
|
const playgroundRoutes = createPlaygroundRoutes();
|
|
85
83
|
const smartScanRoutes = createSmartScanRoutes();
|
|
84
|
+
const settingsRoutes = createSettingsRoutes();
|
|
86
85
|
|
|
87
86
|
app.get('/api/requests', requestsRoutes.getRequests);
|
|
88
87
|
app.get('/api/packets', requestsRoutes.getRequests);
|
|
@@ -138,30 +137,17 @@ export function createUIServer() {
|
|
|
138
137
|
app.post('/api/smartscan/cached-results', smartScanRoutes.getCachedResults);
|
|
139
138
|
app.post('/api/smartscan/cache/clear', smartScanRoutes.clearCache);
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
if (processState.mcpSharkProcess) {
|
|
143
|
-
processState.mcpSharkProcess.kill();
|
|
144
|
-
processState.mcpSharkProcess = null;
|
|
145
|
-
}
|
|
146
|
-
restoreConfig();
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
process.on('SIGTERM', cleanup);
|
|
150
|
-
process.on('SIGINT', cleanup);
|
|
151
|
-
process.on('exit', () => {
|
|
152
|
-
restoreConfig();
|
|
153
|
-
});
|
|
140
|
+
app.get('/api/settings', settingsRoutes.getSettings);
|
|
154
141
|
|
|
155
142
|
const staticPath = path.join(__dirname, 'dist');
|
|
156
143
|
app.use(express.static(staticPath));
|
|
157
144
|
|
|
158
|
-
app.get('*', (
|
|
145
|
+
app.get('*', (_req, res) => {
|
|
159
146
|
res.sendFile(path.join(staticPath, 'index.html'));
|
|
160
147
|
});
|
|
161
148
|
|
|
162
149
|
const notifyClients = async () => {
|
|
163
150
|
const requests = queryRequests(db, { limit: 100 });
|
|
164
|
-
const { serializeBigInt } = await import('./server/utils/serialization.js');
|
|
165
151
|
const message = JSON.stringify({ type: 'update', data: serializeBigInt(requests) });
|
|
166
152
|
clients.forEach((client) => {
|
|
167
153
|
if (client.readyState === 1) {
|
|
@@ -171,7 +157,7 @@ export function createUIServer() {
|
|
|
171
157
|
};
|
|
172
158
|
|
|
173
159
|
const timestampState = { lastTs: 0 };
|
|
174
|
-
setInterval(() => {
|
|
160
|
+
const intervalId = setInterval(() => {
|
|
175
161
|
const lastCheck = db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets').get();
|
|
176
162
|
if (lastCheck && lastCheck.max_ts > timestampState.lastTs) {
|
|
177
163
|
timestampState.lastTs = lastCheck.max_ts;
|
|
@@ -179,19 +165,78 @@ export function createUIServer() {
|
|
|
179
165
|
}
|
|
180
166
|
}, 500);
|
|
181
167
|
|
|
168
|
+
const cleanup = async () => {
|
|
169
|
+
console.log('Shutting down UI server...');
|
|
170
|
+
|
|
171
|
+
// Clear interval
|
|
172
|
+
clearInterval(intervalId);
|
|
173
|
+
|
|
174
|
+
// Stop MCP Shark server if running
|
|
175
|
+
if (processState.mcpSharkServer) {
|
|
176
|
+
try {
|
|
177
|
+
if (processState.mcpSharkServer.stop) {
|
|
178
|
+
await processState.mcpSharkServer.stop();
|
|
179
|
+
}
|
|
180
|
+
processState.mcpSharkServer = null;
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error('Error stopping MCP Shark server:', err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Close WebSocket connections
|
|
187
|
+
clients.forEach((client) => {
|
|
188
|
+
if (client.readyState === 1) {
|
|
189
|
+
client.close();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
clients.clear();
|
|
193
|
+
|
|
194
|
+
// Close WebSocket server
|
|
195
|
+
wss.close();
|
|
196
|
+
|
|
197
|
+
// Restore config
|
|
198
|
+
restoreConfig();
|
|
199
|
+
|
|
200
|
+
// Close HTTP server
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
server.close(() => {
|
|
203
|
+
console.log('UI server stopped');
|
|
204
|
+
resolve();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
182
209
|
return { server, cleanup };
|
|
183
210
|
}
|
|
184
211
|
|
|
185
212
|
export async function runUIServer() {
|
|
186
|
-
const port = parseInt(process.env.UI_PORT) || 9853;
|
|
213
|
+
const port = Number.parseInt(process.env.UI_PORT) || 9853;
|
|
187
214
|
const { server, cleanup } = createUIServer();
|
|
188
215
|
|
|
189
|
-
|
|
190
|
-
|
|
216
|
+
const shutdown = async () => {
|
|
217
|
+
try {
|
|
218
|
+
await cleanup();
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.error('Error during shutdown:', err);
|
|
221
|
+
} finally {
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Register signal handlers
|
|
227
|
+
process.on('SIGTERM', shutdown);
|
|
228
|
+
process.on('SIGINT', shutdown);
|
|
229
|
+
process.on('exit', async () => {
|
|
230
|
+
// Final cleanup on exit
|
|
231
|
+
try {
|
|
232
|
+
await cleanup();
|
|
233
|
+
} catch (_err) {
|
|
234
|
+
// Ignore errors during exit
|
|
235
|
+
}
|
|
191
236
|
});
|
|
192
237
|
|
|
193
|
-
server.
|
|
194
|
-
|
|
238
|
+
server.listen(port, '0.0.0.0', () => {
|
|
239
|
+
console.log(`UI server listening on http://localhost:${port}`);
|
|
195
240
|
});
|
|
196
241
|
}
|
|
197
242
|
|
package/ui/src/App.jsx
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
-
import CompositeSetup from './CompositeSetup';
|
|
3
2
|
import CompositeLogs from './CompositeLogs';
|
|
4
|
-
import
|
|
3
|
+
import CompositeSetup from './CompositeSetup';
|
|
4
|
+
import IntroTour from './IntroTour';
|
|
5
5
|
import SmartScan from './SmartScan';
|
|
6
6
|
import TabNavigation from './TabNavigation';
|
|
7
|
-
import IntroTour from './IntroTour';
|
|
8
7
|
import HelpButton from './components/App/HelpButton';
|
|
9
8
|
import TrafficTab from './components/App/TrafficTab';
|
|
10
|
-
import {
|
|
9
|
+
import { useAppState } from './components/App/useAppState';
|
|
10
|
+
import McpPlayground from './components/McpPlayground';
|
|
11
11
|
import { tourSteps } from './config/tourSteps.jsx';
|
|
12
|
+
import { colors } from './theme';
|
|
12
13
|
import { fadeIn } from './utils/animations';
|
|
13
|
-
import { useAppState } from './components/App/useAppState';
|
|
14
14
|
|
|
15
15
|
function App() {
|
|
16
16
|
const {
|
package/ui/src/CompositeLogs.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { colors } from './theme';
|
|
3
|
-
import LogsToolbar from './components/LogsToolbar';
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
2
|
import LogsDisplay from './components/LogsDisplay';
|
|
3
|
+
import LogsToolbar from './components/LogsToolbar';
|
|
4
|
+
import { colors } from './theme';
|
|
5
5
|
|
|
6
6
|
function CompositeLogs() {
|
|
7
7
|
const [logs, setLogs] = useState([]);
|
|
@@ -11,27 +11,23 @@ function CompositeLogs() {
|
|
|
11
11
|
const logEndRef = useRef(null);
|
|
12
12
|
const wsRef = useRef(null);
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
useEffect(() => {
|
|
15
15
|
if (autoScroll && logEndRef.current) {
|
|
16
16
|
logEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
17
17
|
}
|
|
18
|
-
};
|
|
18
|
+
}, [autoScroll]);
|
|
19
19
|
|
|
20
20
|
useEffect(() => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
console.error('Failed to load logs:', error);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
21
|
+
const loadLogs = async () => {
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch('/api/composite/logs?limit=5000');
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
setLogs(data);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to load logs:', error);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
33
30
|
|
|
34
|
-
useEffect(() => {
|
|
35
31
|
loadLogs();
|
|
36
32
|
|
|
37
33
|
const wsUrl = import.meta.env.DEV
|
|
@@ -106,8 +102,12 @@ function CompositeLogs() {
|
|
|
106
102
|
};
|
|
107
103
|
|
|
108
104
|
const filteredLogs = logs.filter((log) => {
|
|
109
|
-
if (logType !== 'all' && log.type !== logType)
|
|
110
|
-
|
|
105
|
+
if (logType !== 'all' && log.type !== logType) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (filter && !log.line.toLowerCase().includes(filter.toLowerCase())) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
111
|
return true;
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { colors } from './theme';
|
|
3
|
-
import SetupHeader from './components/SetupHeader';
|
|
4
|
-
import ConfigFileSection from './components/ConfigFileSection';
|
|
5
|
-
import WhatThisDoesSection from './components/WhatThisDoesSection';
|
|
6
|
-
import ServerControl from './components/ServerControl';
|
|
7
|
-
import MessageDisplay from './components/MessageDisplay';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
8
2
|
import BackupList from './components/BackupList';
|
|
3
|
+
import ConfigFileSection from './components/ConfigFileSection';
|
|
9
4
|
import ConfigViewerModal from './components/ConfigViewerModal';
|
|
10
|
-
import
|
|
5
|
+
import MessageDisplay from './components/MessageDisplay';
|
|
6
|
+
import ServerControl from './components/ServerControl';
|
|
7
|
+
import SetupHeader from './components/SetupHeader';
|
|
8
|
+
import WhatThisDoesSection from './components/WhatThisDoesSection';
|
|
11
9
|
import { useConfigManagement } from './hooks/useConfigManagement';
|
|
10
|
+
import { useServiceExtraction } from './hooks/useServiceExtraction';
|
|
11
|
+
import { colors } from './theme';
|
|
12
12
|
|
|
13
13
|
function CompositeSetup() {
|
|
14
14
|
const [fileContent, setFileContent] = useState('');
|
|
@@ -181,7 +181,7 @@ function CompositeSetup() {
|
|
|
181
181
|
if (res.ok) {
|
|
182
182
|
const msg = data.message || 'MCP Shark server stopped';
|
|
183
183
|
setMessage(
|
|
184
|
-
data.message
|
|
184
|
+
data.message?.includes('restored')
|
|
185
185
|
? 'MCP Shark server stopped and original config file restored'
|
|
186
186
|
: msg
|
|
187
187
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { colors, fonts } from '../theme';
|
|
2
1
|
import { IconHelp, IconX } from '@tabler/icons-react';
|
|
2
|
+
import { colors, fonts } from '../theme';
|
|
3
3
|
|
|
4
4
|
export default function HelpGuideHeader({ onClose }) {
|
|
5
5
|
return (
|
|
@@ -29,6 +29,7 @@ export default function HelpGuideHeader({ onClose }) {
|
|
|
29
29
|
</h2>
|
|
30
30
|
</div>
|
|
31
31
|
<button
|
|
32
|
+
type="button"
|
|
32
33
|
onClick={onClose}
|
|
33
34
|
style={{
|
|
34
35
|
background: 'transparent',
|
package/ui/src/HelpGuide.jsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { colors } from './theme';
|
|
3
|
-
import HelpGuideHeader from './HelpGuide/HelpGuideHeader';
|
|
4
2
|
import HelpGuideContent from './HelpGuide/HelpGuideContent';
|
|
5
3
|
import HelpGuideFooter from './HelpGuide/HelpGuideFooter';
|
|
4
|
+
import HelpGuideHeader from './HelpGuide/HelpGuideHeader';
|
|
5
|
+
import { colors } from './theme';
|
|
6
6
|
|
|
7
7
|
function HelpGuide({ onClose }) {
|
|
8
8
|
const [dontShowAgain, setDontShowAgain] = useState(false);
|
|
@@ -19,7 +19,9 @@ function HelpGuide({ onClose }) {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<
|
|
22
|
+
<dialog
|
|
23
|
+
open
|
|
24
|
+
aria-modal="true"
|
|
23
25
|
style={{
|
|
24
26
|
position: 'fixed',
|
|
25
27
|
top: 0,
|
|
@@ -32,10 +34,20 @@ function HelpGuide({ onClose }) {
|
|
|
32
34
|
alignItems: 'center',
|
|
33
35
|
justifyContent: 'center',
|
|
34
36
|
padding: '20px',
|
|
37
|
+
border: 'none',
|
|
38
|
+
margin: 0,
|
|
39
|
+
width: '100%',
|
|
40
|
+
height: '100%',
|
|
35
41
|
}}
|
|
36
42
|
onClick={handleClose}
|
|
43
|
+
onKeyDown={(e) => {
|
|
44
|
+
if (e.key === 'Escape') {
|
|
45
|
+
handleClose();
|
|
46
|
+
}
|
|
47
|
+
}}
|
|
37
48
|
>
|
|
38
49
|
<div
|
|
50
|
+
role="document"
|
|
39
51
|
style={{
|
|
40
52
|
background: colors.bgCard,
|
|
41
53
|
border: `1px solid ${colors.borderLight}`,
|
|
@@ -47,6 +59,7 @@ function HelpGuide({ onClose }) {
|
|
|
47
59
|
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.5)',
|
|
48
60
|
}}
|
|
49
61
|
onClick={(e) => e.stopPropagation()}
|
|
62
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
50
63
|
>
|
|
51
64
|
<HelpGuideHeader onClose={handleClose} />
|
|
52
65
|
<div style={{ padding: '24px' }}>
|
|
@@ -58,7 +71,7 @@ function HelpGuide({ onClose }) {
|
|
|
58
71
|
/>
|
|
59
72
|
</div>
|
|
60
73
|
</div>
|
|
61
|
-
</
|
|
74
|
+
</dialog>
|
|
62
75
|
);
|
|
63
76
|
}
|
|
64
77
|
|
package/ui/src/IntroTour.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import TourOverlay from './components/TourOverlay';
|
|
3
3
|
import TourTooltip from './components/TourTooltip';
|
|
4
4
|
|
|
@@ -9,7 +9,9 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
|
|
|
9
9
|
const overlayRef = useRef(null);
|
|
10
10
|
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
if (!highlightedElement)
|
|
12
|
+
if (!highlightedElement) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
const updatePosition = () => {
|
|
15
17
|
if (highlightedElement) {
|
|
@@ -31,10 +33,14 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
|
|
|
31
33
|
}, [highlightedElement]);
|
|
32
34
|
|
|
33
35
|
useEffect(() => {
|
|
34
|
-
if (steps.length === 0)
|
|
36
|
+
if (steps.length === 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
35
39
|
|
|
36
40
|
const step = steps[currentStep];
|
|
37
|
-
if (!step)
|
|
41
|
+
if (!step) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
38
44
|
|
|
39
45
|
if (onStepChange) {
|
|
40
46
|
onStepChange(currentStep);
|
|
@@ -95,7 +101,9 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
|
|
|
95
101
|
|
|
96
102
|
const handleSkip = () => {
|
|
97
103
|
handleComplete();
|
|
98
|
-
if (onSkip)
|
|
104
|
+
if (onSkip) {
|
|
105
|
+
onSkip();
|
|
106
|
+
}
|
|
99
107
|
};
|
|
100
108
|
|
|
101
109
|
if (steps.length === 0 || currentStep >= steps.length) {
|
|
@@ -108,6 +116,7 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
|
|
|
108
116
|
<>
|
|
109
117
|
<div
|
|
110
118
|
ref={overlayRef}
|
|
119
|
+
role="presentation"
|
|
111
120
|
style={{
|
|
112
121
|
position: 'fixed',
|
|
113
122
|
top: 0,
|
|
@@ -118,6 +127,11 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
|
|
|
118
127
|
pointerEvents: 'auto',
|
|
119
128
|
}}
|
|
120
129
|
onClick={handleSkip}
|
|
130
|
+
onKeyDown={(e) => {
|
|
131
|
+
if (e.key === 'Escape') {
|
|
132
|
+
handleSkip();
|
|
133
|
+
}
|
|
134
|
+
}}
|
|
121
135
|
>
|
|
122
136
|
<TourOverlay elementRect={elementRect} />
|
|
123
137
|
</div>
|
package/ui/src/LogDetail.jsx
CHANGED
package/ui/src/LogTable.jsx
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { colors, fonts
|
|
1
|
+
import { colors, fonts } from './theme';
|
|
2
2
|
|
|
3
3
|
function LogTable({ logs, selected, onSelect }) {
|
|
4
4
|
const getStatusColor = (status) => {
|
|
5
|
-
if (status === 'error')
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
if (status === 'error') {
|
|
6
|
+
return colors.error;
|
|
7
|
+
}
|
|
8
|
+
if (status === 'success') {
|
|
9
|
+
return colors.success;
|
|
10
|
+
}
|
|
11
|
+
if (status === 'pending') {
|
|
12
|
+
return colors.warning;
|
|
13
|
+
}
|
|
8
14
|
return colors.textTertiary;
|
|
9
15
|
};
|
|
10
16
|
|
|
@@ -144,6 +150,14 @@ function LogTable({ logs, selected, onSelect }) {
|
|
|
144
150
|
<tr
|
|
145
151
|
key={log.id}
|
|
146
152
|
onClick={() => onSelect(log)}
|
|
153
|
+
onKeyDown={(e) => {
|
|
154
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
onSelect(log);
|
|
157
|
+
}
|
|
158
|
+
}}
|
|
159
|
+
tabIndex={0}
|
|
160
|
+
aria-label={`Select log entry ${log.id}`}
|
|
147
161
|
style={{
|
|
148
162
|
cursor: 'pointer',
|
|
149
163
|
background: selected?.id === log.id ? colors.bgSelected : colors.bgCard,
|
|
@@ -151,10 +165,14 @@ function LogTable({ logs, selected, onSelect }) {
|
|
|
151
165
|
transition: 'background-color 0.15s ease',
|
|
152
166
|
}}
|
|
153
167
|
onMouseEnter={(e) => {
|
|
154
|
-
if (selected?.id !== log.id)
|
|
168
|
+
if (selected?.id !== log.id) {
|
|
169
|
+
e.currentTarget.style.background = colors.bgHover;
|
|
170
|
+
}
|
|
155
171
|
}}
|
|
156
172
|
onMouseLeave={(e) => {
|
|
157
|
-
if (selected?.id !== log.id)
|
|
173
|
+
if (selected?.id !== log.id) {
|
|
174
|
+
e.currentTarget.style.background = colors.bgCard;
|
|
175
|
+
}
|
|
158
176
|
}}
|
|
159
177
|
>
|
|
160
178
|
<td
|