blockmine 1.22.0 → 1.23.1
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/.claude/agents/code-architect.md +34 -0
- package/.claude/agents/code-explorer.md +51 -0
- package/.claude/agents/code-reviewer.md +46 -0
- package/.claude/commands/feature-dev.md +125 -0
- package/.claude/settings.json +5 -1
- package/.claude/settings.local.json +12 -1
- package/.claude/skills/frontend-design/SKILL.md +42 -0
- package/CHANGELOG.md +32 -1
- package/README.md +302 -152
- package/backend/package-lock.json +681 -9
- package/backend/package.json +8 -0
- package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
- package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
- package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
- package/backend/prisma/schema.prisma +70 -1
- package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
- package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
- package/backend/src/api/middleware/auth.js +27 -0
- package/backend/src/api/middleware/botAccess.js +7 -3
- package/backend/src/api/middleware/panelApiAuth.js +135 -0
- package/backend/src/api/routes/aiAssistant.js +995 -0
- package/backend/src/api/routes/auth.js +90 -54
- package/backend/src/api/routes/botCommands.js +107 -0
- package/backend/src/api/routes/botGroups.js +165 -0
- package/backend/src/api/routes/botHistory.js +108 -0
- package/backend/src/api/routes/botPermissions.js +99 -0
- package/backend/src/api/routes/botStatus.js +36 -0
- package/backend/src/api/routes/botUsers.js +162 -0
- package/backend/src/api/routes/bots.js +108 -59
- package/backend/src/api/routes/eventGraphs.js +4 -1
- package/backend/src/api/routes/logs.js +13 -3
- package/backend/src/api/routes/panel.js +3 -3
- package/backend/src/api/routes/panelApiKeys.js +179 -0
- package/backend/src/api/routes/pluginIde.js +1715 -135
- package/backend/src/api/routes/plugins.js +170 -13
- package/backend/src/api/routes/proxies.js +130 -0
- package/backend/src/api/routes/search.js +4 -0
- package/backend/src/api/routes/servers.js +20 -3
- package/backend/src/api/routes/settings.js +5 -0
- package/backend/src/api/routes/system.js +3 -3
- package/backend/src/api/routes/traces.js +131 -0
- package/backend/src/config/debug.config.js +36 -0
- package/backend/src/core/BotHistoryStore.js +180 -0
- package/backend/src/core/BotManager.js +14 -4
- package/backend/src/core/BotProcess.js +1517 -1092
- package/backend/src/core/EventGraphManager.js +194 -280
- package/backend/src/core/GraphExecutionEngine.js +1004 -321
- package/backend/src/core/MessageQueue.js +12 -6
- package/backend/src/core/PluginLoader.js +99 -5
- package/backend/src/core/PluginManager.js +74 -13
- package/backend/src/core/TaskScheduler.js +1 -1
- package/backend/src/core/commands/whois.js +1 -1
- package/backend/src/core/node-registries/actions.js +72 -2
- package/backend/src/core/node-registries/arrays.js +18 -0
- package/backend/src/core/node-registries/data.js +1 -1
- package/backend/src/core/node-registries/events.js +14 -0
- package/backend/src/core/node-registries/logic.js +17 -0
- package/backend/src/core/node-registries/strings.js +34 -0
- package/backend/src/core/node-registries/type.js +25 -0
- package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
- package/backend/src/core/nodes/actions/create_command.js +189 -0
- package/backend/src/core/nodes/actions/delete_command.js +92 -0
- package/backend/src/core/nodes/actions/http_request.js +23 -4
- package/backend/src/core/nodes/actions/send_message.js +2 -12
- package/backend/src/core/nodes/actions/update_command.js +133 -0
- package/backend/src/core/nodes/arrays/join.js +28 -0
- package/backend/src/core/nodes/data/cast.js +2 -1
- package/backend/src/core/nodes/data/string_literal.js +2 -13
- package/backend/src/core/nodes/logic/not.js +22 -0
- package/backend/src/core/nodes/strings/starts_with.js +1 -1
- package/backend/src/core/nodes/strings/to_lower.js +22 -0
- package/backend/src/core/nodes/strings/to_upper.js +22 -0
- package/backend/src/core/nodes/type/to_string.js +32 -0
- package/backend/src/core/services/BotLifecycleService.js +835 -596
- package/backend/src/core/services/CommandExecutionService.js +430 -351
- package/backend/src/core/services/DebugSessionManager.js +347 -0
- package/backend/src/core/services/GraphCollaborationManager.js +501 -0
- package/backend/src/core/services/MinecraftBotManager.js +259 -0
- package/backend/src/core/services/MinecraftViewerService.js +216 -0
- package/backend/src/core/services/TraceCollectorService.js +545 -0
- package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
- package/backend/src/core/system/Transport.js +0 -4
- package/backend/src/core/validation/nodeSchemas.js +6 -6
- package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
- package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
- package/backend/src/real-time/botApi/utils.js +11 -0
- package/backend/src/real-time/panelNamespace.js +387 -0
- package/backend/src/real-time/presence.js +7 -2
- package/backend/src/real-time/socketHandler.js +395 -4
- package/backend/src/server.js +18 -0
- package/frontend/dist/assets/index-DqzDkFsP.js +11210 -0
- package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package-lock.json +9437 -0
- package/frontend/package.json +8 -0
- package/package.json +2 -2
- package/screen/console.png +0 -0
- package/screen/dashboard.png +0 -0
- package/screen/graph_collabe.png +0 -0
- package/screen/graph_live_debug.png +0 -0
- package/screen/management_command.png +0 -0
- package/screen/node_debug_trace.png +0 -0
- package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
- package/screen/websocket.png +0 -0
- package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
- package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
- package/frontend/dist/assets/index-CfTo92bP.css +0 -1
- package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const {
|
|
2
|
+
const { authenticateUniversal, authorize } = require('../middleware/auth');
|
|
3
3
|
const { PrismaClient } = require('@prisma/client');
|
|
4
4
|
const router = express.Router();
|
|
5
5
|
const { pluginManager } = require('../../core/services');
|
|
@@ -27,7 +27,7 @@ router.get('/catalog', async (req, res) => {
|
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
router.post('/check-updates/:botId',
|
|
30
|
+
router.post('/check-updates/:botId', authenticateUniversal, authorize('plugin:update'), async (req, res) => {
|
|
31
31
|
try {
|
|
32
32
|
const botId = parseInt(req.params.botId);
|
|
33
33
|
|
|
@@ -43,7 +43,7 @@ router.post('/check-updates/:botId', authenticate, authorize('plugin:update'), a
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
router.post('/update/:pluginId',
|
|
46
|
+
router.post('/update/:pluginId', authenticateUniversal, authorize('plugin:update'), async (req, res) => {
|
|
47
47
|
try {
|
|
48
48
|
const pluginId = parseInt(req.params.pluginId);
|
|
49
49
|
const { targetTag } = req.body; // Получаем тег из тела запроса (если указан)
|
|
@@ -54,7 +54,7 @@ router.post('/update/:pluginId', authenticate, authorize('plugin:update'), async
|
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
router.post('/:id/clear-data',
|
|
57
|
+
router.post('/:id/clear-data', authenticateUniversal, authorize('plugin:settings:edit'), async (req, res) => {
|
|
58
58
|
try {
|
|
59
59
|
const pluginId = parseInt(req.params.id);
|
|
60
60
|
await pluginManager.clearPluginData(pluginId);
|
|
@@ -65,7 +65,21 @@ router.post('/:id/clear-data', authenticate, authorize('plugin:settings:edit'),
|
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
router.
|
|
68
|
+
router.post('/:id/reload', authenticateUniversal, authorize('plugin:settings:edit'), async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const pluginId = parseInt(req.params.id);
|
|
71
|
+
const updatedPlugin = await pluginManager.reloadLocalPlugin(pluginId);
|
|
72
|
+
res.status(200).json({
|
|
73
|
+
message: 'Плагин перезагружен, настройки сброшены.',
|
|
74
|
+
plugin: updatedPlugin
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`[API Error] /plugins/:id/reload:`, error);
|
|
78
|
+
res.status(500).json({ error: error.message || 'Не удалось перезагрузить плагин.' });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
router.get('/:id/info', authenticateUniversal, authorize('plugin:list'), async (req, res) => {
|
|
69
83
|
try {
|
|
70
84
|
const pluginId = parseInt(req.params.id);
|
|
71
85
|
|
|
@@ -115,7 +129,7 @@ router.get('/:id/info', authenticate, authorize('plugin:list'), async (req, res)
|
|
|
115
129
|
}
|
|
116
130
|
});
|
|
117
131
|
|
|
118
|
-
router.get('/bot/:botId',
|
|
132
|
+
router.get('/bot/:botId', authenticateUniversal, authorize('plugin:list'), async (req, res) => {
|
|
119
133
|
try {
|
|
120
134
|
const botId = parseInt(req.params.botId);
|
|
121
135
|
|
|
@@ -165,23 +179,23 @@ router.get('/bot/:botId', authenticate, authorize('plugin:list'), async (req, re
|
|
|
165
179
|
router.get('/catalog/:name', async (req, res) => {
|
|
166
180
|
try {
|
|
167
181
|
const pluginName = req.params.name;
|
|
168
|
-
|
|
182
|
+
|
|
169
183
|
const catalogResponse = await fetch(getCacheBustedUrl(OFFICIAL_CATALOG_URL));
|
|
170
184
|
if (!catalogResponse.ok) throw new Error(`Failed to fetch catalog, status: ${catalogResponse.status}`);
|
|
171
|
-
|
|
185
|
+
|
|
172
186
|
const catalog = await catalogResponse.json();
|
|
173
187
|
const pluginInfo = catalog.find(p => p.name === pluginName);
|
|
174
|
-
|
|
188
|
+
|
|
175
189
|
if (!pluginInfo) {
|
|
176
190
|
return res.status(404).json({ error: 'Плагин с таким именем не найден в каталоге.' });
|
|
177
191
|
}
|
|
178
192
|
|
|
179
193
|
let readmeContent = pluginInfo.description || 'Описание для этого плагина не предоставлено.';
|
|
180
|
-
|
|
194
|
+
|
|
181
195
|
try {
|
|
182
196
|
const urlParts = new URL(pluginInfo.repoUrl);
|
|
183
197
|
const pathParts = urlParts.pathname.split('/').filter(p => p);
|
|
184
|
-
|
|
198
|
+
|
|
185
199
|
if (pathParts.length >= 2) {
|
|
186
200
|
const owner = pathParts[0];
|
|
187
201
|
const repo = pathParts[1].replace('.git', '');
|
|
@@ -204,10 +218,10 @@ router.get('/catalog/:name', async (req, res) => {
|
|
|
204
218
|
} catch (readmeError) {
|
|
205
219
|
console.error(`[API] Не удалось загрузить README для ${pluginName}:`, readmeError.message);
|
|
206
220
|
}
|
|
207
|
-
|
|
221
|
+
|
|
208
222
|
const finalPluginData = {
|
|
209
223
|
...pluginInfo,
|
|
210
|
-
fullDescription: readmeContent
|
|
224
|
+
fullDescription: readmeContent
|
|
211
225
|
};
|
|
212
226
|
|
|
213
227
|
res.json(finalPluginData);
|
|
@@ -217,4 +231,147 @@ router.get('/catalog/:name', async (req, res) => {
|
|
|
217
231
|
}
|
|
218
232
|
});
|
|
219
233
|
|
|
234
|
+
router.get('/bot/:botId/:pluginName/store', authenticateUniversal, authorize('plugin:list'), async (req, res) => {
|
|
235
|
+
try {
|
|
236
|
+
const botId = parseInt(req.params.botId);
|
|
237
|
+
const pluginName = req.params.pluginName;
|
|
238
|
+
|
|
239
|
+
const storeData = await prisma.pluginDataStore.findMany({
|
|
240
|
+
where: {
|
|
241
|
+
botId,
|
|
242
|
+
pluginName
|
|
243
|
+
},
|
|
244
|
+
select: {
|
|
245
|
+
key: true,
|
|
246
|
+
value: true,
|
|
247
|
+
createdAt: true,
|
|
248
|
+
updatedAt: true
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const result = {};
|
|
253
|
+
storeData.forEach(item => {
|
|
254
|
+
try {
|
|
255
|
+
result[item.key] = JSON.parse(item.value);
|
|
256
|
+
} catch (e) {
|
|
257
|
+
result[item.key] = item.value;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
res.json(result);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error(`[API Error] GET /plugins/bot/:botId/:pluginName/store:`, error);
|
|
264
|
+
res.status(500).json({ error: 'Не удалось получить данные store плагина.' });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
router.get('/bot/:botId/:pluginName/store/:key', authenticateUniversal, authorize('plugin:list'), async (req, res) => {
|
|
269
|
+
try {
|
|
270
|
+
const botId = parseInt(req.params.botId);
|
|
271
|
+
const pluginName = req.params.pluginName;
|
|
272
|
+
const key = req.params.key;
|
|
273
|
+
|
|
274
|
+
const storeItem = await prisma.pluginDataStore.findUnique({
|
|
275
|
+
where: {
|
|
276
|
+
pluginName_botId_key: {
|
|
277
|
+
pluginName,
|
|
278
|
+
botId,
|
|
279
|
+
key
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
select: {
|
|
283
|
+
value: true,
|
|
284
|
+
createdAt: true,
|
|
285
|
+
updatedAt: true
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (!storeItem) {
|
|
290
|
+
return res.status(404).json({ error: 'Ключ не найден в store.' });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let parsedValue;
|
|
294
|
+
try {
|
|
295
|
+
parsedValue = JSON.parse(storeItem.value);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
parsedValue = storeItem.value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
res.json({
|
|
301
|
+
key,
|
|
302
|
+
value: parsedValue,
|
|
303
|
+
createdAt: storeItem.createdAt,
|
|
304
|
+
updatedAt: storeItem.updatedAt
|
|
305
|
+
});
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error(`[API Error] GET /plugins/bot/:botId/:pluginName/store/:key:`, error);
|
|
308
|
+
res.status(500).json({ error: 'Не удалось получить значение из store.' });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
router.put('/bot/:botId/:pluginName/store/:key', authenticateUniversal, authorize('plugin:settings:edit'), async (req, res) => {
|
|
313
|
+
try {
|
|
314
|
+
const botId = parseInt(req.params.botId);
|
|
315
|
+
const pluginName = req.params.pluginName;
|
|
316
|
+
const key = req.params.key;
|
|
317
|
+
const { value } = req.body;
|
|
318
|
+
|
|
319
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
320
|
+
|
|
321
|
+
const storeItem = await prisma.pluginDataStore.upsert({
|
|
322
|
+
where: {
|
|
323
|
+
pluginName_botId_key: {
|
|
324
|
+
pluginName,
|
|
325
|
+
botId,
|
|
326
|
+
key
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
update: {
|
|
330
|
+
value: stringValue
|
|
331
|
+
},
|
|
332
|
+
create: {
|
|
333
|
+
pluginName,
|
|
334
|
+
botId,
|
|
335
|
+
key,
|
|
336
|
+
value: stringValue
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
res.json({
|
|
341
|
+
key,
|
|
342
|
+
value: value,
|
|
343
|
+
updatedAt: storeItem.updatedAt
|
|
344
|
+
});
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(`[API Error] PUT /plugins/bot/:botId/:pluginName/store/:key:`, error);
|
|
347
|
+
res.status(500).json({ error: 'Не удалось сохранить значение в store.' });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
router.delete('/bot/:botId/:pluginName/store/:key', authenticateUniversal, authorize('plugin:settings:edit'), async (req, res) => {
|
|
352
|
+
try {
|
|
353
|
+
const botId = parseInt(req.params.botId);
|
|
354
|
+
const pluginName = req.params.pluginName;
|
|
355
|
+
const key = req.params.key;
|
|
356
|
+
|
|
357
|
+
await prisma.pluginDataStore.delete({
|
|
358
|
+
where: {
|
|
359
|
+
pluginName_botId_key: {
|
|
360
|
+
pluginName,
|
|
361
|
+
botId,
|
|
362
|
+
key
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
res.json({ message: 'Значение удалено из store.' });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (error.code === 'P2025') {
|
|
370
|
+
return res.status(404).json({ error: 'Ключ не найден в store.' });
|
|
371
|
+
}
|
|
372
|
+
console.error(`[API Error] DELETE /plugins/bot/:botId/:pluginName/store/:key:`, error);
|
|
373
|
+
res.status(500).json({ error: 'Не удалось удалить значение из store.' });
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
220
377
|
module.exports = router;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const { PrismaClient } = require('@prisma/client');
|
|
4
|
+
const { authenticateUniversal, authorize } = require('../middleware/auth');
|
|
5
|
+
const prisma = new PrismaClient();
|
|
6
|
+
|
|
7
|
+
router.use(authenticateUniversal);
|
|
8
|
+
|
|
9
|
+
router.get('/', authorize('proxy:list'), async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const proxies = await prisma.proxy.findMany({
|
|
12
|
+
orderBy: { name: 'asc' },
|
|
13
|
+
include: {
|
|
14
|
+
_count: {
|
|
15
|
+
select: { bots: true }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
res.json({
|
|
20
|
+
items: proxies,
|
|
21
|
+
total: proxies.length
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error("[API /api/proxies] Ошибка получения списка прокси:", error);
|
|
25
|
+
res.status(500).json({ error: 'Не удалось получить список прокси' });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
router.get('/:id', authorize('proxy:list'), async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const proxyId = parseInt(req.params.id, 10);
|
|
32
|
+
const proxy = await prisma.proxy.findUnique({
|
|
33
|
+
where: { id: proxyId },
|
|
34
|
+
include: {
|
|
35
|
+
_count: {
|
|
36
|
+
select: { bots: true }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (!proxy) {
|
|
41
|
+
return res.status(404).json({ error: 'Прокси не найден' });
|
|
42
|
+
}
|
|
43
|
+
res.json(proxy);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("[API /api/proxies/:id] Ошибка получения прокси:", error);
|
|
46
|
+
res.status(500).json({ error: 'Не удалось получить прокси' });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
router.post('/', authorize('proxy:create'), async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { name, type, host, port, username, password, note } = req.body;
|
|
53
|
+
if (!name || !host || !port) {
|
|
54
|
+
return res.status(400).json({ error: 'Имя, хост и порт прокси обязательны' });
|
|
55
|
+
}
|
|
56
|
+
if (type && !['socks5', 'http'].includes(type)) {
|
|
57
|
+
return res.status(400).json({ error: 'Тип прокси должен быть socks5 или http' });
|
|
58
|
+
}
|
|
59
|
+
const newProxy = await prisma.proxy.create({
|
|
60
|
+
data: {
|
|
61
|
+
name,
|
|
62
|
+
type: type || 'socks5',
|
|
63
|
+
host,
|
|
64
|
+
port: parseInt(port, 10),
|
|
65
|
+
username: username || null,
|
|
66
|
+
password: password || null,
|
|
67
|
+
note: note || null
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
res.status(201).json(newProxy);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.code === 'P2002') return res.status(409).json({ error: 'Прокси с таким именем уже существует' });
|
|
73
|
+
console.error("[API /api/proxies] Ошибка создания прокси:", error);
|
|
74
|
+
res.status(500).json({ error: 'Не удалось создать прокси' });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
router.put('/:id', authorize('proxy:create'), async (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const proxyId = parseInt(req.params.id, 10);
|
|
81
|
+
if (isNaN(proxyId)) return res.status(400).json({ error: 'Некорректный ID прокси' });
|
|
82
|
+
|
|
83
|
+
const { name, type, host, port, username, password, note } = req.body;
|
|
84
|
+
|
|
85
|
+
if (type && !['socks5', 'http'].includes(type)) {
|
|
86
|
+
return res.status(400).json({ error: 'Тип прокси должен быть socks5 или http' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const dataToUpdate = {};
|
|
90
|
+
if (name !== undefined) dataToUpdate.name = name;
|
|
91
|
+
if (type !== undefined) dataToUpdate.type = type;
|
|
92
|
+
if (host !== undefined) dataToUpdate.host = host;
|
|
93
|
+
if (port !== undefined && port !== '') dataToUpdate.port = parseInt(port, 10);
|
|
94
|
+
if (username !== undefined) dataToUpdate.username = username || null;
|
|
95
|
+
if (password !== undefined) dataToUpdate.password = password || null;
|
|
96
|
+
if (note !== undefined) dataToUpdate.note = note || null;
|
|
97
|
+
|
|
98
|
+
Object.keys(dataToUpdate).forEach(k => { if (dataToUpdate[k] === undefined) delete dataToUpdate[k]; });
|
|
99
|
+
|
|
100
|
+
if (dataToUpdate.name) {
|
|
101
|
+
const existing = await prisma.proxy.findFirst({ where: { name: dataToUpdate.name, id: { not: proxyId } } });
|
|
102
|
+
if (existing) return res.status(409).json({ error: 'Прокси с таким именем уже существует' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const updated = await prisma.proxy.update({ where: { id: proxyId }, data: dataToUpdate });
|
|
106
|
+
res.json(updated);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('[API /api/proxies] Ошибка обновления прокси:', error);
|
|
109
|
+
res.status(500).json({ error: 'Не удалось обновить прокси' });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
router.delete('/:id', authorize('proxy:delete'), async (req, res) => {
|
|
114
|
+
try {
|
|
115
|
+
const proxyId = parseInt(req.params.id, 10);
|
|
116
|
+
|
|
117
|
+
const botsUsingProxy = await prisma.bot.count({ where: { proxyId } });
|
|
118
|
+
if (botsUsingProxy > 0) {
|
|
119
|
+
return res.status(400).json({ error: `Нельзя удалить прокси, так как он используется ${botsUsingProxy} ботом(ами).` });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await prisma.proxy.delete({ where: { id: proxyId } });
|
|
123
|
+
res.status(204).send();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[API /api/proxies] Ошибка удаления прокси:', error);
|
|
126
|
+
res.status(500).json({ error: 'Не удалось удалить прокси' });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
module.exports = router;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
+
const { authenticate } = require('../middleware/auth');
|
|
2
3
|
const router = express.Router();
|
|
3
4
|
const { PrismaClient } = require('@prisma/client');
|
|
4
5
|
const prisma = new PrismaClient();
|
|
5
6
|
|
|
7
|
+
// Все роуты требуют аутентификации
|
|
8
|
+
router.use(authenticate);
|
|
9
|
+
|
|
6
10
|
router.get('/', async (req, res) => {
|
|
7
11
|
const { query } = req.query;
|
|
8
12
|
|
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const { PrismaClient } = require('@prisma/client');
|
|
4
|
-
const {
|
|
4
|
+
const { authenticateUniversal, authorize } = require('../middleware/auth');
|
|
5
5
|
const prisma = new PrismaClient();
|
|
6
6
|
|
|
7
|
-
router.use(
|
|
7
|
+
router.use(authenticateUniversal);
|
|
8
8
|
|
|
9
9
|
router.get('/', authorize('server:list'), async (req, res) => {
|
|
10
10
|
try {
|
|
11
11
|
const servers = await prisma.server.findMany({ orderBy: { name: 'asc' } });
|
|
12
|
-
res.json(
|
|
12
|
+
res.json({
|
|
13
|
+
items: servers,
|
|
14
|
+
total: servers.length
|
|
15
|
+
});
|
|
13
16
|
} catch (error) {
|
|
14
17
|
console.error("[API /api/servers] Ошибка получения списка серверов:", error);
|
|
15
18
|
res.status(500).json({ error: 'Не удалось получить список серверов' });
|
|
16
19
|
}
|
|
17
20
|
});
|
|
18
21
|
|
|
22
|
+
router.get('/:id', authorize('server:list'), async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const serverId = parseInt(req.params.id, 10);
|
|
25
|
+
const server = await prisma.server.findUnique({ where: { id: serverId } });
|
|
26
|
+
if (!server) {
|
|
27
|
+
return res.status(404).json({ error: 'Сервер не найден' });
|
|
28
|
+
}
|
|
29
|
+
res.json(server);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error("[API /api/servers/:id] Ошибка получения сервера:", error);
|
|
32
|
+
res.status(500).json({ error: 'Не удалось получить сервер' });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
19
36
|
router.post('/', authorize('server:create'), async (req, res) => {
|
|
20
37
|
try {
|
|
21
38
|
const { name, host, port, version } = req.body;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
2
3
|
const router = express.Router();
|
|
3
4
|
const { PrismaClient } = require('@prisma/client');
|
|
4
5
|
const PluginService = require('../../core/PluginService');
|
|
5
6
|
|
|
6
7
|
const prisma = new PrismaClient();
|
|
7
8
|
|
|
9
|
+
// Все роуты требуют аутентификации и прав администратора
|
|
10
|
+
router.use(authenticate);
|
|
11
|
+
router.use(authorize('system:settings'));
|
|
12
|
+
|
|
8
13
|
router.get('/plugin-directories', async (req, res) => {
|
|
9
14
|
try {
|
|
10
15
|
const setting = await prisma.setting.findUnique({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const { authenticate } = require('../middleware/auth');
|
|
2
|
+
const { authenticate, authenticateUniversal } = require('../middleware/auth');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const pidusage = require('pidusage');
|
|
5
5
|
const rateLimit = require('express-rate-limit');
|
|
@@ -27,7 +27,7 @@ function getSystemCpuUsage() {
|
|
|
27
27
|
* @desc Получить информацию о здоровье системы
|
|
28
28
|
* @access Требуется авторизация
|
|
29
29
|
*/
|
|
30
|
-
router.get('/health',
|
|
30
|
+
router.get('/health', authenticateUniversal, async (req, res) => {
|
|
31
31
|
try {
|
|
32
32
|
const uptime = process.uptime();
|
|
33
33
|
const serverUptime = (Date.now() - serverStartTime) / 1000;
|
|
@@ -135,7 +135,7 @@ router.get('/health', authenticate, async (req, res) => {
|
|
|
135
135
|
* @desc Получить статистику системы
|
|
136
136
|
* @access Требуется авторизация
|
|
137
137
|
*/
|
|
138
|
-
router.get('/stats',
|
|
138
|
+
router.get('/stats', authenticateUniversal, async (req, res) => {
|
|
139
139
|
try {
|
|
140
140
|
const prisma = req.app.get('prisma') || require('../../lib/prisma');
|
|
141
141
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
3
|
+
const router = express.Router();
|
|
4
|
+
const { getTraceCollector } = require('../../core/services/TraceCollectorService');
|
|
5
|
+
|
|
6
|
+
router.use(authenticate);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/traces/:botId
|
|
10
|
+
* Получить трассировки для бота
|
|
11
|
+
*/
|
|
12
|
+
router.get('/:botId', authorize('management:view'), async (req, res) => {
|
|
13
|
+
try {
|
|
14
|
+
const botId = parseInt(req.params.botId);
|
|
15
|
+
const { limit, status, graphId } = req.query;
|
|
16
|
+
|
|
17
|
+
const options = {
|
|
18
|
+
limit: limit ? parseInt(limit) : 150,
|
|
19
|
+
status: status || null,
|
|
20
|
+
graphId: graphId ? parseInt(graphId) : null,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const traceCollector = getTraceCollector();
|
|
24
|
+
const traces = traceCollector.getTracesForBot(botId, options);
|
|
25
|
+
|
|
26
|
+
res.json({
|
|
27
|
+
success: true,
|
|
28
|
+
traces,
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('[API /traces/:botId] Ошибка:', error);
|
|
32
|
+
res.status(500).json({
|
|
33
|
+
success: false,
|
|
34
|
+
error: error.message,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* GET /api/traces/:botId/:traceId
|
|
41
|
+
* Получить конкретную трассировку по ID
|
|
42
|
+
*/
|
|
43
|
+
router.get('/:botId/:traceId', authorize('management:view'), async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const { traceId } = req.params;
|
|
46
|
+
|
|
47
|
+
const traceCollector = getTraceCollector();
|
|
48
|
+
let trace = traceCollector.getTrace(traceId);
|
|
49
|
+
|
|
50
|
+
// Если не нашли в памяти, пытаемся загрузить из БД
|
|
51
|
+
if (!trace) {
|
|
52
|
+
trace = await traceCollector.loadTraceFromDb(traceId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!trace) {
|
|
56
|
+
return res.status(404).json({
|
|
57
|
+
success: false,
|
|
58
|
+
error: 'Трассировка не найдена',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
res.json({
|
|
63
|
+
success: true,
|
|
64
|
+
trace,
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('[API /traces/:botId/:traceId] Ошибка:', error);
|
|
68
|
+
res.status(500).json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: error.message,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* GET /api/traces/:botId/graph/:graphId/last
|
|
77
|
+
* Получить последнюю трассировку для графа
|
|
78
|
+
* Query params: eventType (опционально) - фильтр по типу события
|
|
79
|
+
*/
|
|
80
|
+
router.get('/:botId/graph/:graphId/last', authorize('management:view'), async (req, res) => {
|
|
81
|
+
try {
|
|
82
|
+
const botId = parseInt(req.params.botId);
|
|
83
|
+
const graphId = parseInt(req.params.graphId);
|
|
84
|
+
const { eventType } = req.query;
|
|
85
|
+
|
|
86
|
+
const traceCollector = getTraceCollector();
|
|
87
|
+
const trace = await traceCollector.getLastTraceForGraph(botId, graphId, eventType);
|
|
88
|
+
|
|
89
|
+
if (!trace) {
|
|
90
|
+
return res.status(404).json({
|
|
91
|
+
success: false,
|
|
92
|
+
error: 'Трассировка не найдена',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
res.json({
|
|
97
|
+
success: true,
|
|
98
|
+
trace,
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('[API /traces/:botId/graph/:graphId/last] Ошибка:', error);
|
|
102
|
+
res.status(500).json({
|
|
103
|
+
success: false,
|
|
104
|
+
error: error.message,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* GET /api/traces/stats
|
|
111
|
+
* Получить статистику трассировок
|
|
112
|
+
*/
|
|
113
|
+
router.get('/stats', authorize('management:view'), async (req, res) => {
|
|
114
|
+
try {
|
|
115
|
+
const traceCollector = getTraceCollector();
|
|
116
|
+
const stats = traceCollector.getStats();
|
|
117
|
+
|
|
118
|
+
res.json({
|
|
119
|
+
success: true,
|
|
120
|
+
stats,
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('[API /traces/stats] Ошибка:', error);
|
|
124
|
+
res.status(500).json({
|
|
125
|
+
success: false,
|
|
126
|
+
error: error.message,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
module.exports = router;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Конфигурация для Live Debug системы
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
// Таймауты (в миллисекундах)
|
|
7
|
+
BREAKPOINT_TIMEOUT: 30000, // 30 секунд - максимальное время ожидания breakpoint response
|
|
8
|
+
STEP_MODE_TIMEOUT: 30000, // 30 секунд - максимальное время ожидания step mode response
|
|
9
|
+
TRACE_CLEANUP_DELAY: 5000, // 5 секунд - задержка перед удалением completed trace
|
|
10
|
+
|
|
11
|
+
// Лимиты
|
|
12
|
+
MAX_TRACE_STEPS: 10000, // Максимальное количество шагов в одном trace
|
|
13
|
+
MAX_COMPLETED_TRACES: 100, // Максимальное количество хранимых completed traces
|
|
14
|
+
MAX_OUTPUT_LENGTH: 30000, // Максимальная длина output при передаче через IPC
|
|
15
|
+
|
|
16
|
+
// IPC Message Types (для type safety)
|
|
17
|
+
IPC_TYPES: {
|
|
18
|
+
EXECUTE_EVENT_GRAPH: 'execute_event_graph',
|
|
19
|
+
EXECUTE_HANDLER: 'execute_handler',
|
|
20
|
+
EXECUTE_COMMAND_REQUEST: 'execute_command_request',
|
|
21
|
+
|
|
22
|
+
DEBUG_CHECK_BREAKPOINT: 'debug:check_breakpoint',
|
|
23
|
+
DEBUG_CHECK_STEP_MODE: 'debug:check_step_mode',
|
|
24
|
+
DEBUG_BREAKPOINT_RESPONSE: 'debug:breakpoint_response',
|
|
25
|
+
|
|
26
|
+
TRACE_COMPLETED: 'trace:completed',
|
|
27
|
+
TRACE_ERROR: 'trace:error',
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Debug Session
|
|
31
|
+
DEBUG_SESSION_IDLE_TIMEOUT: 300000, // 5 минут - таймаут неактивной debug сессии
|
|
32
|
+
|
|
33
|
+
// Trace Storage
|
|
34
|
+
TRACE_RETENTION_TIME: 3600000, // 1 час - время хранения completed traces
|
|
35
|
+
ACTIVE_TRACE_MAX_AGE: 600000, // 10 минут - максимальный возраст active trace
|
|
36
|
+
};
|