@mcp-shark/mcp-shark 1.5.4 → 1.5.5
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 +32 -96
- package/bin/mcp-shark.js +1 -1
- package/core/configs/codex.js +68 -0
- package/core/configs/environment.js +51 -0
- package/{lib/common → core}/configs/index.js +16 -1
- package/core/constants/Defaults.js +15 -0
- package/core/constants/HttpStatus.js +14 -0
- package/core/constants/Server.js +20 -0
- package/core/constants/StatusCodes.js +25 -0
- package/core/constants/index.js +7 -0
- package/core/container/DependencyContainer.js +179 -0
- package/core/db/init.js +33 -0
- package/core/index.js +10 -0
- package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
- package/core/libraries/LoggerLibrary.js +91 -0
- package/core/libraries/SerializationLibrary.js +32 -0
- package/core/libraries/bootstrap-logger.js +19 -0
- package/core/libraries/errors/ApplicationError.js +97 -0
- package/core/libraries/index.js +17 -0
- package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
- package/core/mcp-server/index.js +192 -0
- package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
- package/core/mcp-server/server/external/config.js +75 -0
- package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
- package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
- package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
- package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
- package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
- package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
- package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
- package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
- package/core/mcp-server/server/internal/run.js +53 -0
- package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
- package/core/models/ConversationFilters.js +31 -0
- package/core/models/ExportFormat.js +8 -0
- package/core/models/RequestFilters.js +43 -0
- package/core/models/SessionFilters.js +23 -0
- package/core/models/index.js +8 -0
- package/core/repositories/AuditRepository.js +233 -0
- package/core/repositories/ConversationRepository.js +182 -0
- package/core/repositories/PacketRepository.js +237 -0
- package/core/repositories/SchemaRepository.js +107 -0
- package/core/repositories/SessionRepository.js +59 -0
- package/core/repositories/StatisticsRepository.js +54 -0
- package/core/repositories/index.js +10 -0
- package/core/services/AuditService.js +144 -0
- package/core/services/BackupService.js +222 -0
- package/core/services/ConfigDetectionService.js +89 -0
- package/core/services/ConfigFileService.js +210 -0
- package/core/services/ConfigPatchingService.js +137 -0
- package/core/services/ConfigService.js +250 -0
- package/core/services/ConfigTransformService.js +178 -0
- package/core/services/ConversationService.js +19 -0
- package/core/services/ExportService.js +117 -0
- package/core/services/LogService.js +64 -0
- package/core/services/McpClientService.js +235 -0
- package/core/services/McpDiscoveryService.js +107 -0
- package/core/services/RequestService.js +56 -0
- package/core/services/ScanCacheService.js +242 -0
- package/core/services/ScanService.js +167 -0
- package/core/services/ServerManagementService.js +206 -0
- package/core/services/SessionService.js +34 -0
- package/core/services/SettingsService.js +163 -0
- package/core/services/StatisticsService.js +64 -0
- package/core/services/TokenService.js +94 -0
- package/core/services/index.js +25 -0
- package/core/services/parsers/ConfigParserFactory.js +113 -0
- package/core/services/parsers/JsonConfigParser.js +66 -0
- package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
- package/core/services/parsers/TomlConfigParser.js +87 -0
- package/core/services/parsers/index.js +4 -0
- package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
- package/core/utils/validation.js +77 -0
- package/package.json +14 -11
- package/ui/dist/assets/index-CArYxKxS.js +35 -0
- package/ui/dist/index.html +1 -1
- package/ui/server/controllers/BackupController.js +129 -0
- package/ui/server/controllers/ConfigController.js +92 -0
- package/ui/server/controllers/ConversationController.js +41 -0
- package/ui/server/controllers/LogController.js +44 -0
- package/ui/server/controllers/McpClientController.js +60 -0
- package/ui/server/controllers/McpDiscoveryController.js +44 -0
- package/ui/server/controllers/RequestController.js +129 -0
- package/ui/server/controllers/ScanController.js +122 -0
- package/ui/server/controllers/ServerManagementController.js +134 -0
- package/ui/server/controllers/SessionController.js +57 -0
- package/ui/server/controllers/SettingsController.js +24 -0
- package/ui/server/controllers/StatisticsController.js +54 -0
- package/ui/server/controllers/TokenController.js +58 -0
- package/ui/server/controllers/index.js +17 -0
- package/ui/server/routes/backups/index.js +15 -9
- package/ui/server/routes/composite/index.js +62 -32
- package/ui/server/routes/composite/servers.js +20 -15
- package/ui/server/routes/config.js +13 -172
- package/ui/server/routes/conversations.js +9 -19
- package/ui/server/routes/help.js +4 -3
- package/ui/server/routes/logs.js +14 -26
- package/ui/server/routes/playground.js +11 -174
- package/ui/server/routes/requests.js +12 -232
- package/ui/server/routes/sessions.js +10 -21
- package/ui/server/routes/settings.js +10 -192
- package/ui/server/routes/smartscan.js +26 -15
- package/ui/server/routes/statistics.js +8 -79
- package/ui/server/setup.js +162 -0
- package/ui/server/swagger/paths/backups.js +151 -0
- package/ui/server/swagger/paths/components.js +76 -0
- package/ui/server/swagger/paths/config.js +117 -0
- package/ui/server/swagger/paths/conversations.js +29 -0
- package/ui/server/swagger/paths/help.js +82 -0
- package/ui/server/swagger/paths/logs.js +87 -0
- package/ui/server/swagger/paths/playground.js +49 -0
- package/ui/server/swagger/paths/requests.js +178 -0
- package/ui/server/swagger/paths/serverManagement.js +169 -0
- package/ui/server/swagger/paths/sessions.js +61 -0
- package/ui/server/swagger/paths/settings.js +31 -0
- package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
- package/ui/server/swagger/paths/smartScan/index.js +13 -0
- package/ui/server/swagger/paths/smartScan/scans.js +151 -0
- package/ui/server/swagger/paths/smartScan/token.js +71 -0
- package/ui/server/swagger/paths/statistics.js +40 -0
- package/ui/server/swagger/paths.js +38 -0
- package/ui/server/swagger/swagger.js +37 -0
- package/ui/server/utils/cleanup.js +99 -0
- package/ui/server/utils/config.js +18 -96
- package/ui/server/utils/errorHandler.js +43 -0
- package/ui/server/utils/logger.js +2 -2
- package/ui/server/utils/paths.js +27 -30
- package/ui/server/utils/port.js +21 -21
- package/ui/server/utils/process.js +18 -10
- package/ui/server/utils/processState.js +17 -0
- package/ui/server/utils/signals.js +34 -0
- package/ui/server/websocket/broadcast.js +33 -0
- package/ui/server/websocket/handler.js +52 -0
- package/ui/server.js +51 -230
- package/ui/src/App.jsx +2 -0
- package/ui/src/CompositeSetup.jsx +23 -9
- package/ui/src/PacketFilters.jsx +17 -3
- package/ui/src/components/AlertModal.jsx +116 -0
- package/ui/src/components/App/ApiDocsButton.jsx +57 -0
- package/ui/src/components/App/useAppState.js +43 -1
- package/ui/src/components/BackupList.jsx +27 -3
- package/ui/src/utils/requestPairing.js +35 -36
- package/ui/src/utils/requestUtils.js +1 -0
- package/lib/common/db/init.js +0 -132
- package/lib/common/db/logger.js +0 -349
- package/lib/common/db/query.js +0 -403
- package/lib/common/logger.js +0 -90
- package/mcp-server/index.js +0 -138
- package/mcp-server/lib/server/external/config.js +0 -57
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
- package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
- package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
- package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
- package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
- package/mcp-server/lib/server/internal/run.js +0 -37
- package/mcp-server/mcp-shark.js +0 -22
- package/ui/dist/assets/index-CFHeMNwd.js +0 -35
- package/ui/server/routes/backups/deleteBackup.js +0 -54
- package/ui/server/routes/backups/listBackups.js +0 -75
- package/ui/server/routes/backups/restoreBackup.js +0 -83
- package/ui/server/routes/backups/viewBackup.js +0 -47
- package/ui/server/routes/composite/setup.js +0 -129
- package/ui/server/routes/composite/status.js +0 -7
- package/ui/server/routes/composite/stop.js +0 -39
- package/ui/server/routes/composite/utils.js +0 -45
- package/ui/server/routes/smartscan/discover.js +0 -118
- package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
- package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
- package/ui/server/routes/smartscan/scans/createScan.js +0 -43
- package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
- package/ui/server/routes/smartscan/scans/getScan.js +0 -42
- package/ui/server/routes/smartscan/scans/listScans.js +0 -25
- package/ui/server/routes/smartscan/scans.js +0 -13
- package/ui/server/routes/smartscan/token.js +0 -57
- package/ui/server/utils/config-update.js +0 -240
- package/ui/server/utils/scan-cache/all-results.js +0 -197
- package/ui/server/utils/scan-cache/file-operations.js +0 -107
- package/ui/server/utils/scan-cache/hash.js +0 -47
- package/ui/server/utils/scan-cache/server-operations.js +0 -85
- package/ui/server/utils/scan-cache.js +0 -12
- package/ui/server/utils/smartscan-token.js +0 -43
- /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
- /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
- /package/{mcp-server/lib → core/mcp-server}/server/internal/session.js +0 -0
package/ui/server/utils/port.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { createConnection } from 'node:net';
|
|
2
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const startTime = Date.now();
|
|
4
|
+
function tryConnect(port, host, startTime, timeout, resolve, reject) {
|
|
5
|
+
const socket = createConnection(port, host);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
socket.destroy();
|
|
12
|
-
resolve(true);
|
|
13
|
-
});
|
|
7
|
+
socket.on('connect', () => {
|
|
8
|
+
socket.destroy();
|
|
9
|
+
resolve(true);
|
|
10
|
+
});
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
socket.on('error', (_err) => {
|
|
13
|
+
socket.destroy();
|
|
14
|
+
const elapsed = Date.now() - startTime;
|
|
15
|
+
if (elapsed >= timeout) {
|
|
16
|
+
reject(new Error(`Port ${port} not ready after ${timeout}ms`));
|
|
17
|
+
} else {
|
|
18
|
+
setTimeout(() => tryConnect(port, host, startTime, timeout, resolve, reject), 200);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
export function checkPortReady(port, host = 'localhost', timeout = Defaults.PORT_CHECK_TIMEOUT) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
tryConnect(port, host, startTime, timeout, resolve, reject);
|
|
27
27
|
});
|
|
28
28
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
2
|
+
|
|
3
|
+
const MAX_LOG_LINES = Defaults.MAX_LOG_LINES;
|
|
4
|
+
|
|
5
|
+
function logEntry(mcpSharkLogs, broadcastLogUpdate, type, data) {
|
|
6
|
+
const timestamp = new Date().toISOString();
|
|
7
|
+
const line = data.toString();
|
|
8
|
+
mcpSharkLogs.push({ timestamp, type, line });
|
|
9
|
+
if (mcpSharkLogs.length > MAX_LOG_LINES) {
|
|
10
|
+
mcpSharkLogs.shift();
|
|
11
|
+
}
|
|
12
|
+
broadcastLogUpdate({ timestamp, type, line });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createLogEntryWrapper(mcpSharkLogs, broadcastLogUpdate, type, data) {
|
|
16
|
+
return logEntry(mcpSharkLogs, broadcastLogUpdate, type, data);
|
|
17
|
+
}
|
|
2
18
|
|
|
3
19
|
export function createLogEntry(mcpSharkLogs, broadcastLogUpdate) {
|
|
4
|
-
return
|
|
5
|
-
const timestamp = new Date().toISOString();
|
|
6
|
-
const line = data.toString();
|
|
7
|
-
mcpSharkLogs.push({ timestamp, type, line });
|
|
8
|
-
if (mcpSharkLogs.length > MAX_LOG_LINES) {
|
|
9
|
-
mcpSharkLogs.shift();
|
|
10
|
-
}
|
|
11
|
-
broadcastLogUpdate({ timestamp, type, line });
|
|
12
|
-
};
|
|
20
|
+
return (type, data) => createLogEntryWrapper(mcpSharkLogs, broadcastLogUpdate, type, data);
|
|
13
21
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set MCP Shark server process in state
|
|
3
|
+
* @param {object} processState - Process state object
|
|
4
|
+
* @param {object} server - Server instance
|
|
5
|
+
*/
|
|
6
|
+
export function setMcpSharkProcess(processState, server) {
|
|
7
|
+
processState.mcpSharkServer = server;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get MCP Shark server process from state
|
|
12
|
+
* @param {object} processState - Process state object
|
|
13
|
+
* @returns {object|null} Server instance or null
|
|
14
|
+
*/
|
|
15
|
+
export function getMcpSharkProcess(processState) {
|
|
16
|
+
return processState.mcpSharkServer;
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle shutdown signal (SIGTERM, SIGINT)
|
|
3
|
+
* @param {Function} cleanup - Cleanup function to execute
|
|
4
|
+
* @param {object} logger - Logger instance
|
|
5
|
+
*/
|
|
6
|
+
export async function shutdown(cleanup, logger) {
|
|
7
|
+
try {
|
|
8
|
+
// Set a timeout to force exit if cleanup takes too long
|
|
9
|
+
const timeout = setTimeout(() => {
|
|
10
|
+
logger?.warn('Shutdown timeout reached, forcing exit');
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}, 2000); // 2 second timeout
|
|
13
|
+
|
|
14
|
+
await cleanup();
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
logger?.warn({ error: err.message }, 'Error during shutdown, exiting anyway');
|
|
18
|
+
} finally {
|
|
19
|
+
// Always exit, even if cleanup failed
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle process exit event
|
|
26
|
+
* @param {Function} cleanup - Cleanup function to execute
|
|
27
|
+
*/
|
|
28
|
+
export async function handleExit(cleanup) {
|
|
29
|
+
try {
|
|
30
|
+
await cleanup();
|
|
31
|
+
} catch (_err) {
|
|
32
|
+
// Ignore errors during exit
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RequestFilters } from '#core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Broadcast log update to all connected WebSocket clients
|
|
5
|
+
* @param {Set} clients - Set of connected WebSocket clients
|
|
6
|
+
* @param {object} logEntry - Log entry to broadcast
|
|
7
|
+
*/
|
|
8
|
+
export function broadcastLogUpdate(clients, logEntry) {
|
|
9
|
+
const message = JSON.stringify({ type: 'log', data: logEntry });
|
|
10
|
+
for (const client of clients) {
|
|
11
|
+
if (client.readyState === 1) {
|
|
12
|
+
client.send(message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Notify all connected clients about request updates
|
|
19
|
+
* @param {Set} clients - Set of connected WebSocket clients
|
|
20
|
+
* @param {object} requestService - Request service instance
|
|
21
|
+
* @param {object} serializationLib - Serialization library instance
|
|
22
|
+
*/
|
|
23
|
+
export function notifyClients(clients, requestService, serializationLib) {
|
|
24
|
+
const filters = new RequestFilters({ limit: 100 });
|
|
25
|
+
const requests = requestService.getRequests(filters);
|
|
26
|
+
const serialized = serializationLib.serializeBigInt(requests);
|
|
27
|
+
const message = JSON.stringify({ type: 'update', data: serialized });
|
|
28
|
+
for (const client of clients) {
|
|
29
|
+
if (client.readyState === 1) {
|
|
30
|
+
client.send(message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Server as ServerConstants } from '#core/constants/Server.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handle WebSocket connection with heartbeat and timeout
|
|
5
|
+
* @param {Set} clients - Set of connected WebSocket clients
|
|
6
|
+
* @param {WebSocket} ws - WebSocket connection
|
|
7
|
+
* @param {object} logger - Logger instance
|
|
8
|
+
*/
|
|
9
|
+
export function handleWebSocketConnection(clients, ws, logger) {
|
|
10
|
+
clients.add(ws);
|
|
11
|
+
|
|
12
|
+
// Set up timeout to close stale connections
|
|
13
|
+
let timeoutId = setTimeout(() => {
|
|
14
|
+
if (ws.readyState === 1) {
|
|
15
|
+
logger?.warn('WebSocket connection timeout, closing');
|
|
16
|
+
ws.close();
|
|
17
|
+
}
|
|
18
|
+
}, ServerConstants.WEBSOCKET_TIMEOUT_MS);
|
|
19
|
+
|
|
20
|
+
// Set up heartbeat to keep connection alive
|
|
21
|
+
const heartbeatInterval = setInterval(() => {
|
|
22
|
+
if (ws.readyState === 1) {
|
|
23
|
+
ws.ping();
|
|
24
|
+
} else {
|
|
25
|
+
clearInterval(heartbeatInterval);
|
|
26
|
+
}
|
|
27
|
+
}, ServerConstants.WEBSOCKET_HEARTBEAT_INTERVAL_MS);
|
|
28
|
+
|
|
29
|
+
ws.on('pong', () => {
|
|
30
|
+
// Reset timeout on pong
|
|
31
|
+
clearTimeout(timeoutId);
|
|
32
|
+
timeoutId = setTimeout(() => {
|
|
33
|
+
if (ws.readyState === 1) {
|
|
34
|
+
logger?.warn('WebSocket connection timeout, closing');
|
|
35
|
+
ws.close();
|
|
36
|
+
}
|
|
37
|
+
}, ServerConstants.WEBSOCKET_TIMEOUT_MS);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ws.on('close', () => {
|
|
41
|
+
clearTimeout(timeoutId);
|
|
42
|
+
clearInterval(heartbeatInterval);
|
|
43
|
+
clients.delete(ws);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
ws.on('error', (error) => {
|
|
47
|
+
logger?.error({ error: error.message }, 'WebSocket error');
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
clearInterval(heartbeatInterval);
|
|
50
|
+
clients.delete(ws);
|
|
51
|
+
});
|
|
52
|
+
}
|
package/ui/server.js
CHANGED
|
@@ -1,245 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
import { WebSocketServer } from 'ws';
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
6
2
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { queryRequests } from '#common/db/query';
|
|
10
|
-
import { restoreOriginalConfig } from './server/utils/config.js';
|
|
3
|
+
import { Environment } from '#core/configs/index.js';
|
|
4
|
+
import { bootstrapLogger } from '#core/libraries/index.js';
|
|
11
5
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { createConfigRoutes } from './server/routes/config.js';
|
|
15
|
-
import { createConversationsRoutes } from './server/routes/conversations.js';
|
|
16
|
-
import { createHelpRoutes } from './server/routes/help.js';
|
|
17
|
-
import { createLogsRoutes } from './server/routes/logs.js';
|
|
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';
|
|
22
|
-
import { createSmartScanRoutes } from './server/routes/smartscan.js';
|
|
23
|
-
import { createStatisticsRoutes } from './server/routes/statistics.js';
|
|
24
|
-
import { serializeBigInt } from './server/utils/serialization.js';
|
|
25
|
-
|
|
26
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
-
const __dirname = path.dirname(__filename);
|
|
28
|
-
|
|
29
|
-
const _MAX_LOG_LINES = 10000;
|
|
30
|
-
|
|
31
|
-
export function createUIServer() {
|
|
32
|
-
prepareAppDataSpaces();
|
|
33
|
-
|
|
34
|
-
const db = openDb(getDatabaseFile());
|
|
35
|
-
const app = express();
|
|
36
|
-
const server = createServer(app);
|
|
37
|
-
const wss = new WebSocketServer({ server });
|
|
38
|
-
|
|
39
|
-
app.use(express.json());
|
|
40
|
-
|
|
41
|
-
const clients = new Set();
|
|
42
|
-
const mcpSharkLogs = [];
|
|
43
|
-
const processState = { mcpSharkServer: null };
|
|
44
|
-
|
|
45
|
-
const setMcpSharkProcess = (server) => {
|
|
46
|
-
processState.mcpSharkServer = server;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
wss.on('connection', (ws) => {
|
|
50
|
-
clients.add(ws);
|
|
51
|
-
ws.on('close', () => clients.delete(ws));
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const broadcastLogUpdate = (logEntry) => {
|
|
55
|
-
const message = JSON.stringify({ type: 'log', data: logEntry });
|
|
56
|
-
clients.forEach((client) => {
|
|
57
|
-
if (client.readyState === 1) {
|
|
58
|
-
client.send(message);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const restoreConfig = () => {
|
|
64
|
-
return restoreOriginalConfig(mcpSharkLogs, broadcastLogUpdate);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const requestsRoutes = createRequestsRoutes(db);
|
|
68
|
-
const conversationsRoutes = createConversationsRoutes(db);
|
|
69
|
-
const sessionsRoutes = createSessionsRoutes(db);
|
|
70
|
-
const statisticsRoutes = createStatisticsRoutes(db);
|
|
71
|
-
const logsRoutes = createLogsRoutes(mcpSharkLogs, broadcastLogUpdate);
|
|
72
|
-
const configRoutes = createConfigRoutes();
|
|
73
|
-
const backupRoutes = createBackupRoutes();
|
|
74
|
-
const getMcpSharkProcess = () => processState.mcpSharkServer;
|
|
75
|
-
const compositeRoutes = createCompositeRoutes(
|
|
76
|
-
getMcpSharkProcess,
|
|
77
|
-
setMcpSharkProcess,
|
|
78
|
-
mcpSharkLogs,
|
|
79
|
-
broadcastLogUpdate
|
|
80
|
-
);
|
|
81
|
-
const helpRoutes = createHelpRoutes();
|
|
82
|
-
const playgroundRoutes = createPlaygroundRoutes();
|
|
83
|
-
const smartScanRoutes = createSmartScanRoutes();
|
|
84
|
-
const settingsRoutes = createSettingsRoutes();
|
|
85
|
-
|
|
86
|
-
app.get('/api/requests', requestsRoutes.getRequests);
|
|
87
|
-
app.get('/api/packets', requestsRoutes.getRequests);
|
|
88
|
-
app.get('/api/requests/:frameNumber', requestsRoutes.getRequest);
|
|
89
|
-
app.get('/api/packets/:frameNumber', requestsRoutes.getRequest);
|
|
90
|
-
app.get('/api/requests/export', requestsRoutes.exportRequests);
|
|
91
|
-
app.post('/api/requests/clear', requestsRoutes.clearRequests);
|
|
92
|
-
|
|
93
|
-
app.get('/api/conversations', conversationsRoutes.getConversations);
|
|
94
|
-
|
|
95
|
-
app.get('/api/sessions', sessionsRoutes.getSessions);
|
|
96
|
-
app.get('/api/sessions/:sessionId/requests', sessionsRoutes.getSessionRequests);
|
|
97
|
-
app.get('/api/sessions/:sessionId/packets', sessionsRoutes.getSessionRequests);
|
|
98
|
-
|
|
99
|
-
app.get('/api/statistics', statisticsRoutes.getStatistics);
|
|
100
|
-
|
|
101
|
-
app.get('/api/composite/logs', logsRoutes.getLogs);
|
|
102
|
-
app.post('/api/composite/logs/clear', logsRoutes.clearLogs);
|
|
103
|
-
app.get('/api/composite/logs/export', logsRoutes.exportLogs);
|
|
104
|
-
|
|
105
|
-
app.post('/api/config/services', configRoutes.extractServices);
|
|
106
|
-
app.get('/api/config/read', configRoutes.readConfig);
|
|
107
|
-
app.get('/api/config/detect', configRoutes.detectConfig);
|
|
108
|
-
app.get('/api/config/backups', backupRoutes.listBackups);
|
|
109
|
-
app.get('/api/config/backup/view', backupRoutes.viewBackup);
|
|
110
|
-
app.post('/api/config/restore', (req, res) => {
|
|
111
|
-
backupRoutes.restoreBackup(req, res, mcpSharkLogs, broadcastLogUpdate);
|
|
112
|
-
});
|
|
113
|
-
app.post('/api/config/backup/delete', (req, res) => {
|
|
114
|
-
backupRoutes.deleteBackup(req, res, mcpSharkLogs, broadcastLogUpdate);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
app.post('/api/composite/setup', compositeRoutes.setup);
|
|
118
|
-
app.post('/api/composite/stop', (req, res) => {
|
|
119
|
-
compositeRoutes.stop(req, res, restoreConfig);
|
|
120
|
-
});
|
|
121
|
-
app.get('/api/composite/status', compositeRoutes.getStatus);
|
|
122
|
-
app.get('/api/composite/servers', compositeRoutes.getServers);
|
|
123
|
-
|
|
124
|
-
app.get('/api/help/state', helpRoutes.getState);
|
|
125
|
-
app.post('/api/help/dismiss', helpRoutes.dismiss);
|
|
126
|
-
app.post('/api/help/reset', helpRoutes.reset);
|
|
127
|
-
|
|
128
|
-
app.post('/api/playground/proxy', playgroundRoutes.proxyRequest);
|
|
129
|
-
|
|
130
|
-
app.post('/api/smartscan/scans', smartScanRoutes.createScan);
|
|
131
|
-
app.get('/api/smartscan/scans', smartScanRoutes.listScans);
|
|
132
|
-
app.get('/api/smartscan/scans/:scanId', smartScanRoutes.getScan);
|
|
133
|
-
app.get('/api/smartscan/token', smartScanRoutes.getToken);
|
|
134
|
-
app.post('/api/smartscan/token', smartScanRoutes.saveToken);
|
|
135
|
-
app.get('/api/smartscan/discover', smartScanRoutes.discoverServers);
|
|
136
|
-
app.post('/api/smartscan/scans/batch', smartScanRoutes.createBatchScans);
|
|
137
|
-
app.post('/api/smartscan/cached-results', smartScanRoutes.getCachedResults);
|
|
138
|
-
app.post('/api/smartscan/cache/clear', smartScanRoutes.clearCache);
|
|
139
|
-
|
|
140
|
-
app.get('/api/settings', settingsRoutes.getSettings);
|
|
141
|
-
|
|
142
|
-
const staticPath = path.join(__dirname, 'dist');
|
|
143
|
-
app.use(express.static(staticPath));
|
|
144
|
-
|
|
145
|
-
app.get('*', (_req, res) => {
|
|
146
|
-
res.sendFile(path.join(staticPath, 'index.html'));
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const notifyClients = async () => {
|
|
150
|
-
const requests = queryRequests(db, { limit: 100 });
|
|
151
|
-
const message = JSON.stringify({ type: 'update', data: serializeBigInt(requests) });
|
|
152
|
-
clients.forEach((client) => {
|
|
153
|
-
if (client.readyState === 1) {
|
|
154
|
-
client.send(message);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const timestampState = { lastTs: 0 };
|
|
160
|
-
const intervalId = setInterval(() => {
|
|
161
|
-
const lastCheck = db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets').get();
|
|
162
|
-
if (lastCheck && lastCheck.max_ts > timestampState.lastTs) {
|
|
163
|
-
timestampState.lastTs = lastCheck.max_ts;
|
|
164
|
-
notifyClients();
|
|
165
|
-
}
|
|
166
|
-
}, 500);
|
|
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
|
-
|
|
209
|
-
return { server, cleanup };
|
|
210
|
-
}
|
|
6
|
+
import { createUIServer } from './server/setup.js';
|
|
7
|
+
import { handleExit, shutdown } from './server/utils/signals.js';
|
|
211
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Run UI server with signal handlers
|
|
11
|
+
*/
|
|
212
12
|
export async function runUIServer() {
|
|
213
|
-
const port =
|
|
214
|
-
const { server, cleanup } = createUIServer();
|
|
215
|
-
|
|
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
|
-
};
|
|
13
|
+
const port = Environment.getUiPort();
|
|
14
|
+
const { server, cleanup, logger, wss } = createUIServer();
|
|
225
15
|
|
|
226
16
|
// Register signal handlers
|
|
227
|
-
process.on('SIGTERM', shutdown);
|
|
228
|
-
process.on('SIGINT', shutdown);
|
|
229
|
-
process.on('exit',
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
17
|
+
process.on('SIGTERM', () => shutdown(cleanup, logger));
|
|
18
|
+
process.on('SIGINT', () => shutdown(cleanup, logger));
|
|
19
|
+
process.on('exit', () => handleExit(cleanup));
|
|
20
|
+
|
|
21
|
+
// Handle server errors (e.g., port already in use)
|
|
22
|
+
server.on('error', (error) => {
|
|
23
|
+
if (error.code === 'EADDRINUSE') {
|
|
24
|
+
logger?.error(
|
|
25
|
+
{
|
|
26
|
+
port,
|
|
27
|
+
error: error.message,
|
|
28
|
+
},
|
|
29
|
+
`Port ${port} is already in use. Please stop the existing server or use a different port.`
|
|
30
|
+
);
|
|
31
|
+
bootstrapLogger.error(
|
|
32
|
+
`\n❌ Port ${port} is already in use.\n Please stop the existing server or set MCP_SHARK_PORT environment variable to use a different port.\n`
|
|
33
|
+
);
|
|
34
|
+
} else {
|
|
35
|
+
logger?.error({ error: error.message, stack: error.stack }, 'Server error');
|
|
36
|
+
bootstrapLogger.error({ error: error.message }, 'Server error');
|
|
235
37
|
}
|
|
38
|
+
process.exit(1);
|
|
236
39
|
});
|
|
237
40
|
|
|
41
|
+
// Handle WebSocket server errors
|
|
42
|
+
if (wss) {
|
|
43
|
+
wss.on('error', (error) => {
|
|
44
|
+
logger?.error({ error: error.message }, 'WebSocket server error');
|
|
45
|
+
if (error.code === 'EADDRINUSE') {
|
|
46
|
+
bootstrapLogger.error(
|
|
47
|
+
`\n❌ WebSocket port conflict. Port ${port} is already in use.\n Please stop the existing server or use a different port.\n`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
238
53
|
server.listen(port, '0.0.0.0', () => {
|
|
239
|
-
|
|
54
|
+
logger?.info({ port }, 'UI server listening');
|
|
240
55
|
});
|
|
241
56
|
}
|
|
242
57
|
|
|
243
58
|
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
244
|
-
runUIServer().catch(
|
|
59
|
+
runUIServer().catch((error) => {
|
|
60
|
+
bootstrapLogger.error(
|
|
61
|
+
{ error: error.message, stack: error.stack },
|
|
62
|
+
'Failed to start UI server'
|
|
63
|
+
);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
});
|
|
245
66
|
}
|
package/ui/src/App.jsx
CHANGED
|
@@ -4,6 +4,7 @@ import CompositeSetup from './CompositeSetup';
|
|
|
4
4
|
import IntroTour from './IntroTour';
|
|
5
5
|
import SmartScan from './SmartScan';
|
|
6
6
|
import TabNavigation from './TabNavigation';
|
|
7
|
+
import ApiDocsButton from './components/App/ApiDocsButton';
|
|
7
8
|
import HelpButton from './components/App/HelpButton';
|
|
8
9
|
import TrafficTab from './components/App/TrafficTab';
|
|
9
10
|
import { useAppState } from './components/App/useAppState';
|
|
@@ -83,6 +84,7 @@ function App() {
|
|
|
83
84
|
<div style={{ position: 'relative' }} data-tour="tabs">
|
|
84
85
|
<TabNavigation activeTab={activeTab} onTabChange={setActiveTab} />
|
|
85
86
|
</div>
|
|
87
|
+
<ApiDocsButton />
|
|
86
88
|
<HelpButton
|
|
87
89
|
onClick={() => {
|
|
88
90
|
if (showTour) {
|
|
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
import BackupList from './components/BackupList';
|
|
3
3
|
import ConfigFileSection from './components/ConfigFileSection';
|
|
4
4
|
import ConfigViewerModal from './components/ConfigViewerModal';
|
|
5
|
+
import ConfirmationModal from './components/ConfirmationModal';
|
|
5
6
|
import MessageDisplay from './components/MessageDisplay';
|
|
6
7
|
import ServerControl from './components/ServerControl';
|
|
7
8
|
import SetupHeader from './components/SetupHeader';
|
|
@@ -18,6 +19,8 @@ function CompositeSetup() {
|
|
|
18
19
|
const [loading, setLoading] = useState(false);
|
|
19
20
|
const [message, setMessage] = useState(null);
|
|
20
21
|
const [error, setError] = useState(null);
|
|
22
|
+
const [showRestoreModal, setShowRestoreModal] = useState(false);
|
|
23
|
+
const [pendingRestore, setPendingRestore] = useState({ backupPath: null, originalPath: null });
|
|
21
24
|
|
|
22
25
|
const { services, selectedServices, setSelectedServices } = useServiceExtraction(
|
|
23
26
|
fileContent,
|
|
@@ -52,14 +55,14 @@ function CompositeSetup() {
|
|
|
52
55
|
return () => clearInterval(interval);
|
|
53
56
|
}, []);
|
|
54
57
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const handleRestoreClick = (backupPath, originalPath) => {
|
|
59
|
+
setPendingRestore({ backupPath, originalPath });
|
|
60
|
+
setShowRestoreModal(true);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleRestore = async () => {
|
|
64
|
+
const { backupPath, originalPath } = pendingRestore;
|
|
65
|
+
setShowRestoreModal(false);
|
|
63
66
|
|
|
64
67
|
try {
|
|
65
68
|
const res = await fetch('/api/config/restore', {
|
|
@@ -256,7 +259,7 @@ function CompositeSetup() {
|
|
|
256
259
|
backups={backups}
|
|
257
260
|
loadingBackups={loadingBackups}
|
|
258
261
|
onRefresh={loadBackups}
|
|
259
|
-
onRestore={
|
|
262
|
+
onRestore={handleRestoreClick}
|
|
260
263
|
onView={handleViewBackup}
|
|
261
264
|
onDelete={handleDelete}
|
|
262
265
|
/>
|
|
@@ -264,6 +267,17 @@ function CompositeSetup() {
|
|
|
264
267
|
<WhatThisDoesSection filePath={filePath} updatePath={updatePath} />
|
|
265
268
|
</div>
|
|
266
269
|
|
|
270
|
+
<ConfirmationModal
|
|
271
|
+
isOpen={showRestoreModal}
|
|
272
|
+
onClose={() => setShowRestoreModal(false)}
|
|
273
|
+
onConfirm={handleRestore}
|
|
274
|
+
title="Restore Backup"
|
|
275
|
+
message="Are you sure you want to restore this backup? This will overwrite the current config file."
|
|
276
|
+
confirmText="Restore"
|
|
277
|
+
cancelText="Cancel"
|
|
278
|
+
danger={false}
|
|
279
|
+
/>
|
|
280
|
+
|
|
267
281
|
<ConfigViewerModal
|
|
268
282
|
viewingConfig={viewingConfig}
|
|
269
283
|
configContent={configContent}
|
package/ui/src/PacketFilters.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IconSearch, IconTrash } from '@tabler/icons-react';
|
|
2
2
|
import anime from 'animejs';
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import AlertModal from './components/AlertModal';
|
|
4
5
|
import ConfirmationModal from './components/ConfirmationModal';
|
|
5
6
|
import ExportControls from './components/PacketFilters/ExportControls';
|
|
6
7
|
import FilterInput from './components/PacketFilters/FilterInput';
|
|
@@ -10,6 +11,8 @@ import { fadeIn } from './utils/animations';
|
|
|
10
11
|
function RequestFilters({ filters, onFilterChange, stats, onClear }) {
|
|
11
12
|
const filtersRef = useRef(null);
|
|
12
13
|
const [showClearModal, setShowClearModal] = useState(false);
|
|
14
|
+
const [showAlertModal, setShowAlertModal] = useState(false);
|
|
15
|
+
const [alertMessage, setAlertMessage] = useState('');
|
|
13
16
|
|
|
14
17
|
useEffect(() => {
|
|
15
18
|
if (filtersRef.current) {
|
|
@@ -56,7 +59,8 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
|
|
|
56
59
|
document.body.removeChild(a);
|
|
57
60
|
} catch (error) {
|
|
58
61
|
console.error('Failed to export traffic:', error);
|
|
59
|
-
|
|
62
|
+
setAlertMessage('Failed to export traffic. Please try again.');
|
|
63
|
+
setShowAlertModal(true);
|
|
60
64
|
}
|
|
61
65
|
};
|
|
62
66
|
|
|
@@ -217,11 +221,13 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
|
|
|
217
221
|
}
|
|
218
222
|
} else {
|
|
219
223
|
const error = await response.json();
|
|
220
|
-
|
|
224
|
+
setAlertMessage(`Failed to clear traffic: ${error.error || 'Unknown error'}`);
|
|
225
|
+
setShowAlertModal(true);
|
|
221
226
|
}
|
|
222
227
|
} catch (error) {
|
|
223
228
|
console.error('Failed to clear traffic:', error);
|
|
224
|
-
|
|
229
|
+
setAlertMessage('Failed to clear traffic. Please try again.');
|
|
230
|
+
setShowAlertModal(true);
|
|
225
231
|
}
|
|
226
232
|
}}
|
|
227
233
|
title="Clear All Captured Traffic"
|
|
@@ -230,6 +236,14 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
|
|
|
230
236
|
cancelText="Cancel"
|
|
231
237
|
danger={true}
|
|
232
238
|
/>
|
|
239
|
+
|
|
240
|
+
<AlertModal
|
|
241
|
+
isOpen={showAlertModal}
|
|
242
|
+
onClose={() => setShowAlertModal(false)}
|
|
243
|
+
title="Error"
|
|
244
|
+
message={alertMessage}
|
|
245
|
+
type="error"
|
|
246
|
+
/>
|
|
233
247
|
</div>
|
|
234
248
|
);
|
|
235
249
|
}
|