@mcp-shark/mcp-shark 1.5.4 → 1.5.6
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 +154 -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 +63 -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 +163 -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 +205 -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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository for audit logging operations
|
|
3
|
+
* Handles logging of request and response packets
|
|
4
|
+
*/
|
|
5
|
+
export class AuditRepository {
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
_getTimestampNs() {
|
|
11
|
+
return Number(process.hrtime.bigint());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_getTimestampISO() {
|
|
15
|
+
return new Date().toISOString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_calculateDurationMs(startNs, endNs) {
|
|
19
|
+
return (endNs - startNs) / 1_000_000;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_normalizeSessionId(headers) {
|
|
23
|
+
if (!headers || typeof headers !== 'object') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const sessionHeaderKeys = [
|
|
28
|
+
'mcp-session-id',
|
|
29
|
+
'Mcp-Session-Id',
|
|
30
|
+
'X-MCP-Session-Id',
|
|
31
|
+
'x-mcp-session-id',
|
|
32
|
+
'MCP-Session-Id',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const key of sessionHeaderKeys) {
|
|
36
|
+
if (headers[key]) {
|
|
37
|
+
return headers[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_extractJsonRpcMetadata(bodyJson) {
|
|
45
|
+
if (!bodyJson) {
|
|
46
|
+
return { id: null, method: null, result: null, error: null };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const parsed = typeof bodyJson === 'string' ? JSON.parse(bodyJson) : bodyJson;
|
|
51
|
+
return {
|
|
52
|
+
id: parsed.id !== undefined ? String(parsed.id) : null,
|
|
53
|
+
method: parsed.method || null,
|
|
54
|
+
result: parsed.result ? JSON.stringify(parsed.result) : null,
|
|
55
|
+
error: parsed.error ? JSON.stringify(parsed.error) : null,
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
return { id: null, method: null, result: null, error: null };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_generateInfo(direction, method, url, statusCode, jsonrpcMethod) {
|
|
63
|
+
if (direction === 'request') {
|
|
64
|
+
const rpcInfo = jsonrpcMethod ? ` ${jsonrpcMethod}` : '';
|
|
65
|
+
return `${method} ${url}${rpcInfo}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const rpcInfo = jsonrpcMethod ? ` ${jsonrpcMethod}` : '';
|
|
69
|
+
return `${statusCode}${rpcInfo}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_normalizeBody(body) {
|
|
73
|
+
if (!body) {
|
|
74
|
+
return { bodyRaw: '', bodyJson: null };
|
|
75
|
+
}
|
|
76
|
+
if (typeof body === 'string') {
|
|
77
|
+
return { bodyRaw: body, bodyJson: body };
|
|
78
|
+
}
|
|
79
|
+
if (typeof body === 'object') {
|
|
80
|
+
const raw = JSON.stringify(body);
|
|
81
|
+
return { bodyRaw: raw, bodyJson: raw };
|
|
82
|
+
}
|
|
83
|
+
return { bodyRaw: '', bodyJson: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logRequestPacket(options) {
|
|
87
|
+
const {
|
|
88
|
+
method,
|
|
89
|
+
url,
|
|
90
|
+
headers = {},
|
|
91
|
+
body,
|
|
92
|
+
userAgent = null,
|
|
93
|
+
remoteAddress = null,
|
|
94
|
+
sessionId: providedSessionId = null,
|
|
95
|
+
} = options;
|
|
96
|
+
|
|
97
|
+
const timestampNs = this._getTimestampNs();
|
|
98
|
+
const timestampISO = this._getTimestampISO();
|
|
99
|
+
const sessionId = providedSessionId || this._normalizeSessionId(headers);
|
|
100
|
+
const host = headers.host || headers.Host || null;
|
|
101
|
+
|
|
102
|
+
const { bodyRaw, bodyJson } = this._normalizeBody(body);
|
|
103
|
+
const headersJson = JSON.stringify(headers);
|
|
104
|
+
|
|
105
|
+
const jsonrpc = this._extractJsonRpcMetadata(bodyJson || bodyRaw);
|
|
106
|
+
const jsonrpcId = jsonrpc.id;
|
|
107
|
+
const jsonrpcMethod = jsonrpc.method;
|
|
108
|
+
|
|
109
|
+
const length = Buffer.byteLength(headersJson, 'utf8') + Buffer.byteLength(bodyRaw, 'utf8');
|
|
110
|
+
const info = this._generateInfo('request', method, url, null, jsonrpcMethod);
|
|
111
|
+
|
|
112
|
+
const stmt = this.db.prepare(`
|
|
113
|
+
INSERT INTO packets (
|
|
114
|
+
timestamp_ns,
|
|
115
|
+
timestamp_iso,
|
|
116
|
+
direction,
|
|
117
|
+
protocol,
|
|
118
|
+
session_id,
|
|
119
|
+
method,
|
|
120
|
+
url,
|
|
121
|
+
headers_json,
|
|
122
|
+
body_raw,
|
|
123
|
+
body_json,
|
|
124
|
+
jsonrpc_id,
|
|
125
|
+
jsonrpc_method,
|
|
126
|
+
length,
|
|
127
|
+
info,
|
|
128
|
+
user_agent,
|
|
129
|
+
remote_address,
|
|
130
|
+
host
|
|
131
|
+
) VALUES (?, ?, 'request', 'HTTP', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
132
|
+
`);
|
|
133
|
+
|
|
134
|
+
const result = stmt.run(
|
|
135
|
+
timestampNs,
|
|
136
|
+
timestampISO,
|
|
137
|
+
sessionId,
|
|
138
|
+
method,
|
|
139
|
+
url,
|
|
140
|
+
headersJson,
|
|
141
|
+
bodyRaw,
|
|
142
|
+
bodyJson,
|
|
143
|
+
jsonrpcId,
|
|
144
|
+
jsonrpcMethod,
|
|
145
|
+
length,
|
|
146
|
+
info,
|
|
147
|
+
userAgent,
|
|
148
|
+
remoteAddress,
|
|
149
|
+
host
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const frameNumber = result.lastInsertRowid;
|
|
153
|
+
|
|
154
|
+
return { frameNumber, timestampNs, jsonrpcId, sessionId };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
logResponsePacket(options) {
|
|
158
|
+
const {
|
|
159
|
+
statusCode,
|
|
160
|
+
headers = {},
|
|
161
|
+
body,
|
|
162
|
+
requestFrameNumber: _requestFrameNumber = null,
|
|
163
|
+
requestTimestampNs: _requestTimestampNs = null,
|
|
164
|
+
jsonrpcId = null,
|
|
165
|
+
userAgent = null,
|
|
166
|
+
remoteAddress = null,
|
|
167
|
+
sessionId: providedSessionId = null,
|
|
168
|
+
} = options;
|
|
169
|
+
|
|
170
|
+
const timestampNs = this._getTimestampNs();
|
|
171
|
+
const timestampISO = this._getTimestampISO();
|
|
172
|
+
const sessionId = providedSessionId || this._normalizeSessionId(headers);
|
|
173
|
+
const host = headers.host || headers.Host || null;
|
|
174
|
+
|
|
175
|
+
const { bodyRaw, bodyJson } = this._normalizeBody(body);
|
|
176
|
+
const headersJson = JSON.stringify(headers);
|
|
177
|
+
|
|
178
|
+
const jsonrpc = this._extractJsonRpcMetadata(bodyJson || bodyRaw);
|
|
179
|
+
const jsonrpcIdFromBody = jsonrpc.id || jsonrpcId;
|
|
180
|
+
const jsonrpcMethod = jsonrpc.method;
|
|
181
|
+
const jsonrpcResult = jsonrpc.result;
|
|
182
|
+
const jsonrpcError = jsonrpc.error;
|
|
183
|
+
|
|
184
|
+
const length = Buffer.byteLength(headersJson, 'utf8') + Buffer.byteLength(bodyRaw, 'utf8');
|
|
185
|
+
const info = this._generateInfo('response', null, null, statusCode, jsonrpcMethod);
|
|
186
|
+
|
|
187
|
+
const stmt = this.db.prepare(`
|
|
188
|
+
INSERT INTO packets (
|
|
189
|
+
timestamp_ns,
|
|
190
|
+
timestamp_iso,
|
|
191
|
+
direction,
|
|
192
|
+
protocol,
|
|
193
|
+
session_id,
|
|
194
|
+
status_code,
|
|
195
|
+
headers_json,
|
|
196
|
+
body_raw,
|
|
197
|
+
body_json,
|
|
198
|
+
jsonrpc_id,
|
|
199
|
+
jsonrpc_method,
|
|
200
|
+
jsonrpc_result,
|
|
201
|
+
jsonrpc_error,
|
|
202
|
+
length,
|
|
203
|
+
info,
|
|
204
|
+
user_agent,
|
|
205
|
+
remote_address,
|
|
206
|
+
host
|
|
207
|
+
) VALUES (?, ?, 'response', 'HTTP', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
208
|
+
`);
|
|
209
|
+
|
|
210
|
+
const result = stmt.run(
|
|
211
|
+
timestampNs,
|
|
212
|
+
timestampISO,
|
|
213
|
+
sessionId,
|
|
214
|
+
statusCode,
|
|
215
|
+
headersJson,
|
|
216
|
+
bodyRaw,
|
|
217
|
+
bodyJson,
|
|
218
|
+
jsonrpcIdFromBody,
|
|
219
|
+
jsonrpcMethod,
|
|
220
|
+
jsonrpcResult,
|
|
221
|
+
jsonrpcError,
|
|
222
|
+
length,
|
|
223
|
+
info,
|
|
224
|
+
userAgent,
|
|
225
|
+
remoteAddress,
|
|
226
|
+
host
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const frameNumber = result.lastInsertRowid;
|
|
230
|
+
|
|
231
|
+
return { frameNumber, timestampNs, jsonrpcId: jsonrpcIdFromBody, sessionId };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Repository for conversation-related database operations
|
|
5
|
+
*/
|
|
6
|
+
export class ConversationRepository {
|
|
7
|
+
constructor(db) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get conversation flow (request/response pairs)
|
|
13
|
+
*/
|
|
14
|
+
queryConversations(filters = {}) {
|
|
15
|
+
const {
|
|
16
|
+
sessionId = null,
|
|
17
|
+
method = null,
|
|
18
|
+
status = null,
|
|
19
|
+
startTime = null,
|
|
20
|
+
endTime = null,
|
|
21
|
+
jsonrpcId = null,
|
|
22
|
+
limit = Defaults.DEFAULT_LIMIT,
|
|
23
|
+
offset = Defaults.DEFAULT_OFFSET,
|
|
24
|
+
} = filters;
|
|
25
|
+
|
|
26
|
+
const queryParts = [
|
|
27
|
+
'SELECT',
|
|
28
|
+
' c.*,',
|
|
29
|
+
' req.frame_number as req_frame,',
|
|
30
|
+
' req.timestamp_iso as req_timestamp_iso,',
|
|
31
|
+
' req.method as req_method,',
|
|
32
|
+
' req.url as req_url,',
|
|
33
|
+
' req.jsonrpc_method as req_jsonrpc_method,',
|
|
34
|
+
' req.body_json as req_body_json,',
|
|
35
|
+
' req.headers_json as req_headers_json,',
|
|
36
|
+
' resp.frame_number as resp_frame,',
|
|
37
|
+
' resp.timestamp_iso as resp_timestamp_iso,',
|
|
38
|
+
' resp.status_code as resp_status_code,',
|
|
39
|
+
' resp.jsonrpc_method as resp_jsonrpc_method,',
|
|
40
|
+
' resp.body_json as resp_body_json,',
|
|
41
|
+
' resp.headers_json as resp_headers_json,',
|
|
42
|
+
' resp.jsonrpc_result as resp_jsonrpc_result,',
|
|
43
|
+
' resp.jsonrpc_error as resp_jsonrpc_error',
|
|
44
|
+
'FROM conversations c',
|
|
45
|
+
'LEFT JOIN packets req ON c.request_frame_number = req.frame_number',
|
|
46
|
+
'LEFT JOIN packets resp ON c.response_frame_number = resp.frame_number',
|
|
47
|
+
'WHERE 1=1',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const params = [];
|
|
51
|
+
|
|
52
|
+
if (sessionId) {
|
|
53
|
+
queryParts.push('AND c.session_id = ?');
|
|
54
|
+
params.push(sessionId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (method) {
|
|
58
|
+
queryParts.push('AND c.method = ?');
|
|
59
|
+
params.push(method);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (status) {
|
|
63
|
+
queryParts.push('AND c.status = ?');
|
|
64
|
+
params.push(status);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (startTime) {
|
|
68
|
+
queryParts.push('AND c.request_timestamp_ns >= ?');
|
|
69
|
+
params.push(startTime);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (endTime) {
|
|
73
|
+
queryParts.push('AND c.request_timestamp_ns <= ?');
|
|
74
|
+
params.push(endTime);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (jsonrpcId) {
|
|
78
|
+
queryParts.push('AND c.jsonrpc_id = ?');
|
|
79
|
+
params.push(jsonrpcId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
queryParts.push('ORDER BY c.request_timestamp_ns ASC LIMIT ? OFFSET ?');
|
|
83
|
+
params.push(limit, offset);
|
|
84
|
+
|
|
85
|
+
const query = queryParts.join(' ');
|
|
86
|
+
const stmt = this.db.prepare(query);
|
|
87
|
+
return stmt.all(...params);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create conversation entry for request
|
|
92
|
+
*/
|
|
93
|
+
createConversation(frameNumber, sessionId, jsonrpcId, method, timestampNs) {
|
|
94
|
+
const stmt = this.db.prepare(`
|
|
95
|
+
INSERT INTO conversations (
|
|
96
|
+
request_frame_number,
|
|
97
|
+
session_id,
|
|
98
|
+
jsonrpc_id,
|
|
99
|
+
method,
|
|
100
|
+
request_timestamp_ns,
|
|
101
|
+
status
|
|
102
|
+
) VALUES (?, ?, ?, ?, ?, 'pending')
|
|
103
|
+
`);
|
|
104
|
+
stmt.run(frameNumber, sessionId, jsonrpcId, method, timestampNs);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Update conversation entry with response
|
|
109
|
+
*/
|
|
110
|
+
updateConversationWithResponse(
|
|
111
|
+
requestFrameNumber,
|
|
112
|
+
responseFrameNumber,
|
|
113
|
+
timestampNs,
|
|
114
|
+
durationMs,
|
|
115
|
+
status
|
|
116
|
+
) {
|
|
117
|
+
const stmt = this.db.prepare(`
|
|
118
|
+
UPDATE conversations
|
|
119
|
+
SET response_frame_number = ?,
|
|
120
|
+
response_timestamp_ns = ?,
|
|
121
|
+
duration_ms = ?,
|
|
122
|
+
status = ?
|
|
123
|
+
WHERE request_frame_number = ?
|
|
124
|
+
`);
|
|
125
|
+
stmt.run(responseFrameNumber, timestampNs, durationMs, status, requestFrameNumber);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Find conversation by JSON-RPC ID
|
|
130
|
+
*/
|
|
131
|
+
findConversationByJsonRpcId(jsonrpcId) {
|
|
132
|
+
const stmt = this.db.prepare(`
|
|
133
|
+
SELECT request_frame_number FROM conversations
|
|
134
|
+
WHERE jsonrpc_id = ? AND response_frame_number IS NULL
|
|
135
|
+
ORDER BY request_timestamp_ns DESC
|
|
136
|
+
LIMIT 1
|
|
137
|
+
`);
|
|
138
|
+
return stmt.get(jsonrpcId);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get conversation statistics
|
|
143
|
+
*/
|
|
144
|
+
getConversationStatistics(filters = {}) {
|
|
145
|
+
const { sessionId = null, startTime = null, endTime = null } = filters;
|
|
146
|
+
|
|
147
|
+
const whereParts = ['WHERE 1=1'];
|
|
148
|
+
const params = [];
|
|
149
|
+
|
|
150
|
+
if (sessionId) {
|
|
151
|
+
whereParts.push('AND session_id = ?');
|
|
152
|
+
params.push(sessionId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (startTime) {
|
|
156
|
+
whereParts.push('AND request_timestamp_ns >= ?');
|
|
157
|
+
params.push(startTime);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (endTime) {
|
|
161
|
+
whereParts.push('AND request_timestamp_ns <= ?');
|
|
162
|
+
params.push(endTime);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const whereClause = whereParts.join(' ');
|
|
166
|
+
const statsQuery = `
|
|
167
|
+
SELECT
|
|
168
|
+
COUNT(*) as total_conversations,
|
|
169
|
+
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
|
170
|
+
COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending,
|
|
171
|
+
COUNT(CASE WHEN status = 'error' THEN 1 END) as errors,
|
|
172
|
+
AVG(duration_ms) as avg_duration_ms,
|
|
173
|
+
MIN(duration_ms) as min_duration_ms,
|
|
174
|
+
MAX(duration_ms) as max_duration_ms
|
|
175
|
+
FROM conversations
|
|
176
|
+
${whereClause}
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
const stmt = this.db.prepare(statsQuery);
|
|
180
|
+
return stmt.get(...params);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Defaults } from '#core/constants/Defaults.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Repository for packet-related database operations
|
|
5
|
+
* Encapsulates all database queries for packets
|
|
6
|
+
*/
|
|
7
|
+
export class PacketRepository {
|
|
8
|
+
constructor(db) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
queryPackets(filters = {}) {
|
|
13
|
+
const {
|
|
14
|
+
sessionId = null,
|
|
15
|
+
direction = null,
|
|
16
|
+
method = null,
|
|
17
|
+
jsonrpcMethod = null,
|
|
18
|
+
statusCode = null,
|
|
19
|
+
startTime = null,
|
|
20
|
+
endTime = null,
|
|
21
|
+
jsonrpcId = null,
|
|
22
|
+
limit = Defaults.DEFAULT_LIMIT,
|
|
23
|
+
offset = Defaults.DEFAULT_OFFSET,
|
|
24
|
+
} = filters;
|
|
25
|
+
|
|
26
|
+
const queryParts = ['SELECT * FROM packets WHERE 1=1'];
|
|
27
|
+
const params = [];
|
|
28
|
+
|
|
29
|
+
if (sessionId) {
|
|
30
|
+
queryParts.push('AND session_id = ?');
|
|
31
|
+
params.push(sessionId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (direction) {
|
|
35
|
+
queryParts.push('AND direction = ?');
|
|
36
|
+
params.push(direction);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (method) {
|
|
40
|
+
queryParts.push('AND method = ?');
|
|
41
|
+
params.push(method);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (jsonrpcMethod) {
|
|
45
|
+
queryParts.push('AND jsonrpc_method = ?');
|
|
46
|
+
params.push(jsonrpcMethod);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (statusCode !== null && statusCode !== undefined) {
|
|
50
|
+
queryParts.push('AND status_code = ?');
|
|
51
|
+
params.push(statusCode);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (startTime) {
|
|
55
|
+
queryParts.push('AND timestamp_ns >= ?');
|
|
56
|
+
params.push(startTime);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (endTime) {
|
|
60
|
+
queryParts.push('AND timestamp_ns <= ?');
|
|
61
|
+
params.push(endTime);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (jsonrpcId) {
|
|
65
|
+
queryParts.push('AND jsonrpc_id = ?');
|
|
66
|
+
params.push(jsonrpcId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
queryParts.push('ORDER BY timestamp_ns ASC LIMIT ? OFFSET ?');
|
|
70
|
+
params.push(limit, offset);
|
|
71
|
+
|
|
72
|
+
const query = queryParts.join(' ');
|
|
73
|
+
const stmt = this.db.prepare(query);
|
|
74
|
+
return stmt.all(...params);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
queryRequests(filters = {}) {
|
|
78
|
+
const {
|
|
79
|
+
sessionId = null,
|
|
80
|
+
direction = null,
|
|
81
|
+
method = null,
|
|
82
|
+
jsonrpcMethod = null,
|
|
83
|
+
statusCode = null,
|
|
84
|
+
startTime = null,
|
|
85
|
+
endTime = null,
|
|
86
|
+
jsonrpcId = null,
|
|
87
|
+
search = null,
|
|
88
|
+
serverName = null,
|
|
89
|
+
limit = Defaults.DEFAULT_LIMIT,
|
|
90
|
+
offset = Defaults.DEFAULT_OFFSET,
|
|
91
|
+
} = filters;
|
|
92
|
+
|
|
93
|
+
const queryParts = ['SELECT * FROM packets WHERE 1=1'];
|
|
94
|
+
const params = [];
|
|
95
|
+
|
|
96
|
+
if (search) {
|
|
97
|
+
const searchPattern = `%${search}%`;
|
|
98
|
+
const serverNamePattern = `%"name":"${search}%`;
|
|
99
|
+
queryParts.push(`AND (
|
|
100
|
+
session_id LIKE ? ESCAPE '\\' OR
|
|
101
|
+
method LIKE ? ESCAPE '\\' OR
|
|
102
|
+
url LIKE ? ESCAPE '\\' OR
|
|
103
|
+
jsonrpc_method LIKE ? ESCAPE '\\' OR
|
|
104
|
+
jsonrpc_id LIKE ? ESCAPE '\\' OR
|
|
105
|
+
info LIKE ? ESCAPE '\\' OR
|
|
106
|
+
body_raw LIKE ? ESCAPE '\\' OR
|
|
107
|
+
body_json LIKE ? ESCAPE '\\' OR
|
|
108
|
+
headers_json LIKE ? ESCAPE '\\' OR
|
|
109
|
+
host LIKE ? ESCAPE '\\' OR
|
|
110
|
+
remote_address LIKE ? ESCAPE '\\' OR
|
|
111
|
+
body_json LIKE ? ESCAPE '\\' OR
|
|
112
|
+
body_raw LIKE ? ESCAPE '\\'
|
|
113
|
+
)`);
|
|
114
|
+
const searchParams = Array.from({ length: 11 }, () => searchPattern);
|
|
115
|
+
params.push(...searchParams);
|
|
116
|
+
params.push(serverNamePattern);
|
|
117
|
+
params.push(serverNamePattern);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (sessionId) {
|
|
121
|
+
queryParts.push("AND session_id LIKE ? ESCAPE '\\'");
|
|
122
|
+
params.push(`%${sessionId}%`);
|
|
123
|
+
}
|
|
124
|
+
if (direction) {
|
|
125
|
+
queryParts.push('AND direction = ?');
|
|
126
|
+
params.push(direction);
|
|
127
|
+
}
|
|
128
|
+
if (method) {
|
|
129
|
+
queryParts.push("AND method LIKE ? ESCAPE '\\'");
|
|
130
|
+
params.push(`%${method}%`);
|
|
131
|
+
}
|
|
132
|
+
if (jsonrpcMethod) {
|
|
133
|
+
queryParts.push("AND jsonrpc_method LIKE ? ESCAPE '\\'");
|
|
134
|
+
params.push(`%${jsonrpcMethod}%`);
|
|
135
|
+
}
|
|
136
|
+
if (statusCode !== null && statusCode !== undefined) {
|
|
137
|
+
queryParts.push('AND status_code = ?');
|
|
138
|
+
params.push(statusCode);
|
|
139
|
+
}
|
|
140
|
+
if (startTime) {
|
|
141
|
+
queryParts.push('AND timestamp_ns >= ?');
|
|
142
|
+
params.push(startTime);
|
|
143
|
+
}
|
|
144
|
+
if (endTime) {
|
|
145
|
+
queryParts.push('AND timestamp_ns <= ?');
|
|
146
|
+
params.push(endTime);
|
|
147
|
+
}
|
|
148
|
+
if (jsonrpcId) {
|
|
149
|
+
queryParts.push("AND jsonrpc_id LIKE ? ESCAPE '\\'");
|
|
150
|
+
params.push(`%${jsonrpcId}%`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (serverName) {
|
|
154
|
+
const serverPattern = `%"name":"${serverName}.%`;
|
|
155
|
+
const serverPattern2 = `%"name":"${serverName}"%`;
|
|
156
|
+
queryParts.push(`AND (
|
|
157
|
+
body_json LIKE ? ESCAPE '\\' OR
|
|
158
|
+
body_raw LIKE ? ESCAPE '\\' OR
|
|
159
|
+
body_json LIKE ? ESCAPE '\\' OR
|
|
160
|
+
body_raw LIKE ? ESCAPE '\\'
|
|
161
|
+
)`);
|
|
162
|
+
params.push(serverPattern);
|
|
163
|
+
params.push(serverPattern);
|
|
164
|
+
params.push(serverPattern2);
|
|
165
|
+
params.push(serverPattern2);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
queryParts.push('ORDER BY timestamp_ns DESC LIMIT ? OFFSET ?');
|
|
169
|
+
params.push(limit, offset);
|
|
170
|
+
|
|
171
|
+
const query = queryParts.join(' ');
|
|
172
|
+
const stmt = this.db.prepare(query);
|
|
173
|
+
return stmt.all(...params);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getByFrameNumber(frameNumber) {
|
|
177
|
+
const stmt = this.db.prepare('SELECT * FROM packets WHERE frame_number = ?');
|
|
178
|
+
return stmt.get(frameNumber);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
getSessionPackets(sessionId, limit = Defaults.DEFAULT_SESSION_LIMIT) {
|
|
182
|
+
const stmt = this.db.prepare(`
|
|
183
|
+
SELECT * FROM packets
|
|
184
|
+
WHERE session_id = ?
|
|
185
|
+
ORDER BY timestamp_ns ASC
|
|
186
|
+
LIMIT ?
|
|
187
|
+
`);
|
|
188
|
+
return stmt.all(sessionId, limit);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getSessionRequests(sessionId, limit = Defaults.DEFAULT_SESSION_LIMIT) {
|
|
192
|
+
const stmt = this.db.prepare(`
|
|
193
|
+
SELECT * FROM packets
|
|
194
|
+
WHERE session_id = ?
|
|
195
|
+
ORDER BY timestamp_ns DESC
|
|
196
|
+
LIMIT ?
|
|
197
|
+
`);
|
|
198
|
+
return stmt.all(sessionId, limit);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
clearAll() {
|
|
202
|
+
this.db.exec('PRAGMA foreign_keys = OFF');
|
|
203
|
+
|
|
204
|
+
const tablesResult = this.db
|
|
205
|
+
.prepare(
|
|
206
|
+
`
|
|
207
|
+
SELECT name FROM sqlite_master
|
|
208
|
+
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
209
|
+
`
|
|
210
|
+
)
|
|
211
|
+
.all();
|
|
212
|
+
|
|
213
|
+
const existingTables = tablesResult.map((row) => row.name);
|
|
214
|
+
const trafficTables = ['conversations', 'packets', 'sessions'];
|
|
215
|
+
|
|
216
|
+
const clearedTables = [];
|
|
217
|
+
for (const table of trafficTables) {
|
|
218
|
+
if (existingTables.includes(table)) {
|
|
219
|
+
try {
|
|
220
|
+
this.db.exec(`DELETE FROM ${table}`);
|
|
221
|
+
clearedTables.push(table);
|
|
222
|
+
} catch (_err) {
|
|
223
|
+
// Log error but continue
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.db.exec('PRAGMA foreign_keys = ON');
|
|
229
|
+
|
|
230
|
+
return { clearedTables };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getMaxTimestamp() {
|
|
234
|
+
const stmt = this.db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets');
|
|
235
|
+
return stmt.get();
|
|
236
|
+
}
|
|
237
|
+
}
|