queen-mq 0.1.0 ā 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +862 -752
- package/AUTH.md +2044 -0
- package/LICENSE.md +202 -0
- package/README.md +1705 -1051
- package/WEBAPP.md +1889 -0
- package/assets/dashboard-01.png +0 -0
- package/assets/queen-logo-blue.svg +210 -0
- package/assets/queen-logo-cyan.svg +210 -0
- package/assets/queen-logo-indigo.svg +210 -0
- package/assets/queen-logo-orange.svg +210 -0
- package/assets/queen-logo-pink.svg +210 -0
- package/assets/queen-logo-purple.svg +210 -0
- package/assets/queen-logo-rose.svg +239 -0
- package/assets/queen-logo.svg +263 -0
- package/examples/batch-processing.js +58 -0
- package/examples/test-complete-client.js +260 -0
- package/examples/test-dashboard-api.js +200 -0
- package/examples/test-traceid.js +147 -0
- package/package.json +17 -4
- package/server.log +1 -0
- package/src/benchmark/consumer.js +207 -0
- package/src/benchmark/consumer_multi.js +216 -0
- package/src/benchmark/producer.js +75 -0
- package/src/benchmark/producer_multi.js +115 -0
- package/src/client/client.js +300 -31
- package/src/client/queenClient.js +5 -0
- package/src/cluster-server.js +242 -0
- package/src/config.js +19 -5
- package/src/database/connection.js +42 -16
- package/src/database/poolManager.js +7 -0
- package/src/database/schema-v2.sql +194 -130
- package/src/managers/queueManagerOptimized.js +823 -933
- package/src/managers/systemEventManager.js +8 -3
- package/src/routes/messages.js +127 -57
- package/src/routes/pop.js +27 -43
- package/src/routes/resources.js +61 -27
- package/src/routes/status.js +1037 -0
- package/src/server.js +308 -272
- package/src/services/evictionService.js +57 -28
- package/src/services/retentionService.js +44 -11
- package/src/test/MIGRATION_ISSUES.md +174 -0
- package/src/test/README.md +203 -0
- package/src/test/advanced-pattern-tests.js +1137 -0
- package/src/test/bus-mode-tests.js +361 -0
- package/src/test/core-tests.js +342 -0
- package/src/test/edge-case-tests.js +561 -0
- package/src/test/enterprise-tests.js +637 -0
- package/src/test/partition-locking-tests.js +545 -0
- package/src/test/test-new.js +278 -0
- package/src/test/test.js +6 -3
- package/src/test/utils.js +169 -0
- package/src/utils/streaming.js +231 -0
- package/src/utils/uuid.js +2 -2
- package/src/websocket/wsServer.js +10 -3
- package/test-keepalive-v2.sh +22 -0
- package/webapp/COLOR_GUIDE.md +118 -0
- package/webapp/README.md +143 -0
- package/webapp/index.html +14 -0
- package/webapp/package-lock.json +3184 -0
- package/webapp/package.json +25 -0
- package/webapp/postcss.config.js +7 -0
- package/webapp/public/assets/queen-logo-blue.svg +210 -0
- package/webapp/public/assets/queen-logo-cyan.svg +210 -0
- package/webapp/public/assets/queen-logo-indigo.svg +210 -0
- package/webapp/public/assets/queen-logo-orange.svg +210 -0
- package/webapp/public/assets/queen-logo-pink.svg +210 -0
- package/webapp/public/assets/queen-logo-purple.svg +210 -0
- package/webapp/public/assets/queen-logo-rose.svg +239 -0
- package/webapp/public/assets/queen-logo.svg +263 -0
- package/webapp/src/App.vue +19 -0
- package/webapp/src/api/analytics.js +10 -0
- package/webapp/src/api/client.js +29 -0
- package/webapp/src/api/consumers.js +52 -0
- package/webapp/src/api/health.js +7 -0
- package/webapp/src/api/messages.js +26 -0
- package/webapp/src/api/queues.js +14 -0
- package/webapp/src/api/resources.js +8 -0
- package/webapp/src/assets/styles/main.css +357 -0
- package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
- package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
- package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
- package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
- package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
- package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
- package/webapp/src/components/common/ConfirmDialog.vue +56 -0
- package/webapp/src/components/common/LoadingSpinner.vue +6 -0
- package/webapp/src/components/common/MetricCard.vue +43 -0
- package/webapp/src/components/common/StatusBadge.vue +45 -0
- package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
- package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
- package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
- package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
- package/webapp/src/components/layout/AppLayout.vue +110 -0
- package/webapp/src/components/layout/AppSidebar.vue +304 -0
- package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
- package/webapp/src/components/messages/MessageFilters.vue +114 -0
- package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
- package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
- package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
- package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
- package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
- package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
- package/webapp/src/components/queues/QueueFilters.vue +90 -0
- package/webapp/src/composables/useApi.js +34 -0
- package/webapp/src/composables/useTheme.js +36 -0
- package/webapp/src/main.js +11 -0
- package/webapp/src/router/index.js +42 -0
- package/webapp/src/utils/colors.js +96 -0
- package/webapp/src/utils/formatters.js +49 -0
- package/webapp/src/views/Analytics.vue +377 -0
- package/webapp/src/views/ConsumerGroups.vue +433 -0
- package/webapp/src/views/Dashboard.vue +418 -0
- package/webapp/src/views/Messages.vue +361 -0
- package/webapp/src/views/QueueDetail.vue +582 -0
- package/webapp/src/views/Queues.vue +496 -0
- package/webapp/tailwind.config.js +25 -0
- package/webapp/vite.config.js +10 -0
- package/CACHE.md +0 -519
- package/DASHBOARD-V3.md +0 -478
- package/DASHBOARD.md +0 -382
- package/MOD_QUEUE.md +0 -453
- package/PARTITION_LOCKING_DESIGN.md +0 -989
- package/PLAN.md +0 -707
- package/QUERY_ANALSYS.md +0 -72
- package/QUEUE_BUS.md +0 -334
- package/V2-PLAN.md +0 -236
- package/dashboard/.vscode/extensions.json +0 -3
- package/dashboard/README.md +0 -5
- package/dashboard/index.html +0 -14
- package/dashboard/package-lock.json +0 -1458
- package/dashboard/package.json +0 -25
- package/dashboard/public/vite.svg +0 -1
- package/dashboard/src/App.vue +0 -29
- package/dashboard/src/assets/styles/main.css +0 -908
- package/dashboard/src/assets/vue.svg +0 -1
- package/dashboard/src/components/cards/MetricCard.vue +0 -298
- package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
- package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
- package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
- package/dashboard/src/components/common/ActivityFeed.vue +0 -251
- package/dashboard/src/components/layout/AppHeader.vue +0 -208
- package/dashboard/src/components/layout/AppLayout.vue +0 -88
- package/dashboard/src/components/layout/AppSidebar.vue +0 -261
- package/dashboard/src/main.js +0 -44
- package/dashboard/src/router.js +0 -54
- package/dashboard/src/services/api.js +0 -187
- package/dashboard/src/services/websocket.js +0 -167
- package/dashboard/src/utils/constants.js +0 -56
- package/dashboard/src/utils/helpers.js +0 -118
- package/dashboard/src/views/Analytics.vue +0 -912
- package/dashboard/src/views/Dashboard.vue +0 -906
- package/dashboard/src/views/Messages.vue +0 -437
- package/dashboard/src/views/QueueDetail.vue +0 -501
- package/dashboard/src/views/Queues.vue +0 -333
- package/dashboard/vite.config.js +0 -30
- package/debug-namespace.js +0 -110
- package/docs/long-polling.md +0 -159
- package/docs/multi-server-cache-solutions.md +0 -185
- package/docs/performance-tuning.md +0 -222
- package/src/routes/analytics.js +0 -812
package/src/server.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import uWS from 'uWebSockets.js';
|
|
2
|
-
import pg from 'pg';
|
|
3
2
|
import fs from 'fs';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import { fileURLToPath } from 'url';
|
|
6
5
|
import config from './config.js';
|
|
7
|
-
import { initDatabase } from './database/connection.js';
|
|
6
|
+
import { createPool, initDatabase } from './database/connection.js';
|
|
8
7
|
import { createOptimizedQueueManager } from './managers/queueManagerOptimized.js';
|
|
9
8
|
import { createResourceCache } from './managers/resourceCache.js';
|
|
10
9
|
import { createEventManager } from './managers/eventManager.js';
|
|
@@ -15,14 +14,15 @@ import { createPushRoute } from './routes/push.js';
|
|
|
15
14
|
import { createPopRoute } from './routes/pop.js';
|
|
16
15
|
import { createAckRoute } from './routes/ack.js';
|
|
17
16
|
import { createConfigureRoute } from './routes/configure.js';
|
|
18
|
-
import { createAnalyticsRoutes } from './routes/analytics.js';
|
|
19
17
|
import { createMessagesRoutes } from './routes/messages.js';
|
|
20
18
|
import { createResourcesRoutes } from './routes/resources.js';
|
|
19
|
+
import { createStatusRoutes } from './routes/status.js';
|
|
21
20
|
import { createWebSocketServer } from './websocket/wsServer.js';
|
|
22
21
|
import { initEncryption } from './services/encryptionService.js';
|
|
23
22
|
import { startRetentionJob } from './services/retentionService.js';
|
|
24
23
|
import { startEvictionJob } from './services/evictionService.js';
|
|
25
24
|
import { log } from './utils/logger.js';
|
|
25
|
+
import { streamJSON, sendJSON, sendError } from './utils/streaming.js';
|
|
26
26
|
|
|
27
27
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
28
|
|
|
@@ -38,26 +38,11 @@ let requestCount = 0;
|
|
|
38
38
|
let messageCount = 0;
|
|
39
39
|
const startTime = Date.now();
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
const createOptimizedPool = () => {
|
|
44
|
-
return new Pool({
|
|
45
|
-
user: config.DATABASE.USER,
|
|
46
|
-
host: config.DATABASE.HOST,
|
|
47
|
-
database: config.DATABASE.DATABASE,
|
|
48
|
-
password: config.DATABASE.PASSWORD,
|
|
49
|
-
port: config.DATABASE.PORT,
|
|
50
|
-
max: config.DATABASE.POOL_SIZE,
|
|
51
|
-
idleTimeoutMillis: config.DATABASE.IDLE_TIMEOUT,
|
|
52
|
-
connectionTimeoutMillis: config.DATABASE.CONNECTION_TIMEOUT,
|
|
53
|
-
statement_timeout: config.DATABASE.STATEMENT_TIMEOUT,
|
|
54
|
-
query_timeout: config.DATABASE.QUERY_TIMEOUT,
|
|
55
|
-
application_name: config.SERVER.APPLICATION_NAME
|
|
56
|
-
});
|
|
57
|
-
};
|
|
41
|
+
// Shutdown flag to prevent background jobs from running during shutdown
|
|
42
|
+
let isShuttingDown = false;
|
|
58
43
|
|
|
59
44
|
// Initialize components
|
|
60
|
-
const pool =
|
|
45
|
+
const pool = createPool();
|
|
61
46
|
const resourceCache = createResourceCache();
|
|
62
47
|
const eventManager = createEventManager();
|
|
63
48
|
|
|
@@ -69,9 +54,9 @@ resourceCache.registerEventHandlers(systemEventManager);
|
|
|
69
54
|
|
|
70
55
|
// Always use optimized queue manager - it's better for all scenarios
|
|
71
56
|
const queueManager = createOptimizedQueueManager(pool, resourceCache, eventManager, systemEventManager);
|
|
72
|
-
const analyticsRoutes = createAnalyticsRoutes(queueManager);
|
|
73
57
|
const messagesRoutes = createMessagesRoutes(pool, queueManager);
|
|
74
|
-
const resourcesRoutes = createResourcesRoutes(pool);
|
|
58
|
+
const resourcesRoutes = createResourcesRoutes(pool, systemEventManager);
|
|
59
|
+
const statusRoutes = createStatusRoutes(pool);
|
|
75
60
|
|
|
76
61
|
// Initialize database with migrations
|
|
77
62
|
const initDatabaseWithMigrations = async () => {
|
|
@@ -136,14 +121,17 @@ const stopRetention = startRetentionJob(pool);
|
|
|
136
121
|
const stopEviction = startEvictionJob(pool, eventManager);
|
|
137
122
|
|
|
138
123
|
// Background job for lease reclamation
|
|
139
|
-
setInterval(async () => {
|
|
124
|
+
const leaseReclaimInterval = setInterval(async () => {
|
|
125
|
+
if (isShuttingDown) return;
|
|
140
126
|
try {
|
|
141
127
|
const reclaimed = await queueManager.reclaimExpiredLeases();
|
|
142
128
|
if (reclaimed > 0) {
|
|
143
129
|
log(`Reclaimed ${reclaimed} expired leases`);
|
|
144
130
|
}
|
|
145
131
|
} catch (error) {
|
|
146
|
-
|
|
132
|
+
if (!isShuttingDown) {
|
|
133
|
+
log('Error reclaiming leases:', error);
|
|
134
|
+
}
|
|
147
135
|
}
|
|
148
136
|
}, config.JOBS.LEASE_RECLAIM_INTERVAL);
|
|
149
137
|
|
|
@@ -154,13 +142,27 @@ const app = uWS.App();
|
|
|
154
142
|
const wsServer = createWebSocketServer(app, eventManager);
|
|
155
143
|
|
|
156
144
|
// Background job for queue depth updates
|
|
157
|
-
setInterval(async () => {
|
|
158
|
-
|
|
145
|
+
const queueDepthInterval = setInterval(async () => {
|
|
146
|
+
if (isShuttingDown) return;
|
|
147
|
+
try {
|
|
148
|
+
await wsServer.updateQueueDepths(queueManager);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (!isShuttingDown) {
|
|
151
|
+
log('Error updating queue depths:', error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
159
154
|
}, config.JOBS.QUEUE_DEPTH_UPDATE_INTERVAL);
|
|
160
155
|
|
|
161
156
|
// Background job for system stats
|
|
162
|
-
setInterval(async () => {
|
|
163
|
-
|
|
157
|
+
const systemStatsInterval = setInterval(async () => {
|
|
158
|
+
if (isShuttingDown) return;
|
|
159
|
+
try {
|
|
160
|
+
await wsServer.sendSystemStats(pool);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!isShuttingDown) {
|
|
163
|
+
log('Error sending system stats:', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
164
166
|
}, config.JOBS.SYSTEM_STATS_UPDATE_INTERVAL);
|
|
165
167
|
|
|
166
168
|
// CORS headers helper
|
|
@@ -363,21 +365,26 @@ app.get('/api/v1/pop/queue/:queue/partition/:partition', (res, req) => {
|
|
|
363
365
|
});
|
|
364
366
|
}
|
|
365
367
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
res
|
|
370
|
-
} else {
|
|
368
|
+
// Use streaming with backpressure for large responses
|
|
369
|
+
if (result.messages.length === 0) {
|
|
370
|
+
res.cork(() => {
|
|
371
|
+
setCorsHeaders(res);
|
|
371
372
|
res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
|
|
372
|
-
}
|
|
373
|
-
}
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
res.cork(() => {
|
|
376
|
+
setCorsHeaders(res);
|
|
377
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString());
|
|
378
|
+
});
|
|
379
|
+
streamJSON(res, result, { aborted });
|
|
380
|
+
}
|
|
374
381
|
}).catch(error => {
|
|
375
382
|
if (aborted) return;
|
|
376
383
|
log('Pop error:', error);
|
|
377
384
|
res.cork(() => {
|
|
378
385
|
setCorsHeaders(res);
|
|
379
|
-
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
380
386
|
});
|
|
387
|
+
sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
|
381
388
|
});
|
|
382
389
|
});
|
|
383
390
|
|
|
@@ -415,21 +422,26 @@ app.get('/api/v1/pop/queue/:queue', (res, req) => {
|
|
|
415
422
|
});
|
|
416
423
|
}
|
|
417
424
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
res
|
|
422
|
-
} else {
|
|
425
|
+
// Use streaming with backpressure for large responses
|
|
426
|
+
if (result.messages.length === 0) {
|
|
427
|
+
res.cork(() => {
|
|
428
|
+
setCorsHeaders(res);
|
|
423
429
|
res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
|
|
424
|
-
}
|
|
425
|
-
}
|
|
430
|
+
});
|
|
431
|
+
} else {
|
|
432
|
+
res.cork(() => {
|
|
433
|
+
setCorsHeaders(res);
|
|
434
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString());
|
|
435
|
+
});
|
|
436
|
+
streamJSON(res, result, { aborted });
|
|
437
|
+
}
|
|
426
438
|
}).catch(error => {
|
|
427
439
|
if (aborted) return;
|
|
428
440
|
log('Pop error:', error);
|
|
429
441
|
res.cork(() => {
|
|
430
442
|
setCorsHeaders(res);
|
|
431
|
-
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
432
443
|
});
|
|
444
|
+
sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
|
433
445
|
});
|
|
434
446
|
});
|
|
435
447
|
|
|
@@ -470,21 +482,26 @@ app.get('/api/v1/pop', (res, req) => {
|
|
|
470
482
|
});
|
|
471
483
|
}
|
|
472
484
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
res
|
|
477
|
-
} else {
|
|
485
|
+
// Use streaming with backpressure for large responses
|
|
486
|
+
if (result.messages.length === 0) {
|
|
487
|
+
res.cork(() => {
|
|
488
|
+
setCorsHeaders(res);
|
|
478
489
|
res.writeStatus(config.HTTP_STATUS.NO_CONTENT.toString()).end();
|
|
479
|
-
}
|
|
480
|
-
}
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
res.cork(() => {
|
|
493
|
+
setCorsHeaders(res);
|
|
494
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString());
|
|
495
|
+
});
|
|
496
|
+
streamJSON(res, result, { aborted });
|
|
497
|
+
}
|
|
481
498
|
}).catch(error => {
|
|
482
499
|
if (aborted) return;
|
|
483
500
|
log('Pop error:', error);
|
|
484
501
|
res.cork(() => {
|
|
485
502
|
setCorsHeaders(res);
|
|
486
|
-
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
487
503
|
});
|
|
504
|
+
sendError(res, error, config.HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
|
488
505
|
});
|
|
489
506
|
});
|
|
490
507
|
|
|
@@ -575,30 +592,34 @@ app.post('/api/v1/ack/batch', (res, req) => {
|
|
|
575
592
|
}, abortedRef);
|
|
576
593
|
});
|
|
577
594
|
|
|
578
|
-
//
|
|
579
|
-
app.get('/api/v1/
|
|
595
|
+
// Messages routes
|
|
596
|
+
app.get('/api/v1/messages', (res, req) => {
|
|
580
597
|
const query = new URLSearchParams(req.getQuery());
|
|
581
|
-
const fromDateTime = query.get('fromDateTime');
|
|
582
|
-
const toDateTime = query.get('toDateTime');
|
|
583
|
-
const queue = query.get('queue');
|
|
584
|
-
const namespace = query.get('namespace');
|
|
585
|
-
const task = query.get('task');
|
|
586
598
|
|
|
587
599
|
let aborted = false;
|
|
588
600
|
res.onAborted(() => {
|
|
589
601
|
aborted = true;
|
|
590
|
-
log('
|
|
602
|
+
log('Messages list request aborted');
|
|
591
603
|
});
|
|
592
604
|
|
|
593
|
-
|
|
605
|
+
const filters = {
|
|
606
|
+
ns: query.get('ns'),
|
|
607
|
+
task: query.get('task'),
|
|
608
|
+
queue: query.get('queue'),
|
|
609
|
+
status: query.get('status'),
|
|
610
|
+
limit: parseInt(query.get('limit') || config.API.DEFAULT_LIMIT.toString()),
|
|
611
|
+
offset: parseInt(query.get('offset') || config.API.DEFAULT_OFFSET.toString())
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
messagesRoutes.listMessages(filters).then(result => {
|
|
594
615
|
if (aborted) return;
|
|
595
616
|
res.cork(() => {
|
|
596
617
|
setCorsHeaders(res);
|
|
597
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
618
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ messages: result }));
|
|
598
619
|
});
|
|
599
620
|
}).catch(error => {
|
|
600
621
|
if (aborted) return;
|
|
601
|
-
log('
|
|
622
|
+
log('List messages error:', error);
|
|
602
623
|
res.cork(() => {
|
|
603
624
|
setCorsHeaders(res);
|
|
604
625
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -606,19 +627,16 @@ app.get('/api/v1/analytics/queues', (res, req) => {
|
|
|
606
627
|
});
|
|
607
628
|
});
|
|
608
629
|
|
|
609
|
-
app.get('/api/v1/
|
|
610
|
-
const
|
|
611
|
-
const query = new URLSearchParams(req.getQuery());
|
|
612
|
-
const fromDateTime = query.get('fromDateTime');
|
|
613
|
-
const toDateTime = query.get('toDateTime');
|
|
630
|
+
app.get('/api/v1/messages/:transactionId', (res, req) => {
|
|
631
|
+
const transactionId = req.getParameter(0);
|
|
614
632
|
|
|
615
633
|
let aborted = false;
|
|
616
634
|
res.onAborted(() => {
|
|
617
635
|
aborted = true;
|
|
618
|
-
log('
|
|
636
|
+
log('Get message request aborted');
|
|
619
637
|
});
|
|
620
638
|
|
|
621
|
-
|
|
639
|
+
messagesRoutes.getMessage(transactionId).then(result => {
|
|
622
640
|
if (aborted) return;
|
|
623
641
|
res.cork(() => {
|
|
624
642
|
setCorsHeaders(res);
|
|
@@ -626,37 +644,25 @@ app.get('/api/v1/analytics/queue/:queue', (res, req) => {
|
|
|
626
644
|
});
|
|
627
645
|
}).catch(error => {
|
|
628
646
|
if (aborted) return;
|
|
629
|
-
log('
|
|
647
|
+
log('Get message error:', error);
|
|
630
648
|
res.cork(() => {
|
|
631
649
|
setCorsHeaders(res);
|
|
632
|
-
res.writeStatus(config.HTTP_STATUS.
|
|
650
|
+
res.writeStatus(error.message === 'Message not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
|
|
651
|
+
.end(JSON.stringify({ error: error.message }));
|
|
633
652
|
});
|
|
634
653
|
});
|
|
635
654
|
});
|
|
636
655
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
const query = new URLSearchParams(req.getQuery());
|
|
640
|
-
const namespace = query.get('namespace');
|
|
641
|
-
const task = query.get('task');
|
|
642
|
-
const fromDateTime = query.get('fromDateTime');
|
|
643
|
-
const toDateTime = query.get('toDateTime');
|
|
644
|
-
const queue = query.get('queue');
|
|
656
|
+
app.del('/api/v1/messages/:transactionId', (res, req) => {
|
|
657
|
+
const transactionId = req.getParameter(0);
|
|
645
658
|
|
|
646
659
|
let aborted = false;
|
|
647
660
|
res.onAborted(() => {
|
|
648
661
|
aborted = true;
|
|
649
|
-
log('
|
|
662
|
+
log('Delete message request aborted');
|
|
650
663
|
});
|
|
651
664
|
|
|
652
|
-
|
|
653
|
-
const getStats = namespace
|
|
654
|
-
? analyticsRoutes.getNamespaceStats(namespace, filters)
|
|
655
|
-
: task
|
|
656
|
-
? analyticsRoutes.getTaskStats(task, filters)
|
|
657
|
-
: analyticsRoutes.getQueues({ ...filters, queue, namespace, task });
|
|
658
|
-
|
|
659
|
-
getStats.then(result => {
|
|
665
|
+
messagesRoutes.deleteMessage(transactionId).then(result => {
|
|
660
666
|
if (aborted) return;
|
|
661
667
|
res.cork(() => {
|
|
662
668
|
setCorsHeaders(res);
|
|
@@ -664,29 +670,25 @@ app.get('/api/v1/analytics', (res, req) => {
|
|
|
664
670
|
});
|
|
665
671
|
}).catch(error => {
|
|
666
672
|
if (aborted) return;
|
|
667
|
-
log('
|
|
673
|
+
log('Delete message error:', error);
|
|
668
674
|
res.cork(() => {
|
|
669
675
|
setCorsHeaders(res);
|
|
670
|
-
res.writeStatus(config.HTTP_STATUS.
|
|
676
|
+
res.writeStatus(error.message === 'Message not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
|
|
677
|
+
.end(JSON.stringify({ error: error.message }));
|
|
671
678
|
});
|
|
672
679
|
});
|
|
673
680
|
});
|
|
674
681
|
|
|
675
|
-
app.
|
|
676
|
-
const
|
|
677
|
-
const fromDateTime = query.get('fromDateTime');
|
|
678
|
-
const toDateTime = query.get('toDateTime');
|
|
679
|
-
const queue = query.get('queue');
|
|
680
|
-
const namespace = query.get('namespace');
|
|
681
|
-
const task = query.get('task');
|
|
682
|
+
app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
|
|
683
|
+
const transactionId = req.getParameter(0);
|
|
682
684
|
|
|
683
685
|
let aborted = false;
|
|
684
686
|
res.onAborted(() => {
|
|
685
687
|
aborted = true;
|
|
686
|
-
log('
|
|
688
|
+
log('Retry message request aborted');
|
|
687
689
|
});
|
|
688
690
|
|
|
689
|
-
|
|
691
|
+
messagesRoutes.retryMessage(transactionId).then(result => {
|
|
690
692
|
if (aborted) return;
|
|
691
693
|
res.cork(() => {
|
|
692
694
|
setCorsHeaders(res);
|
|
@@ -694,7 +696,7 @@ app.get('/api/v1/analytics/queue-depths', (res, req) => {
|
|
|
694
696
|
});
|
|
695
697
|
}).catch(error => {
|
|
696
698
|
if (aborted) return;
|
|
697
|
-
log('
|
|
699
|
+
log('Retry message error:', error);
|
|
698
700
|
res.cork(() => {
|
|
699
701
|
setCorsHeaders(res);
|
|
700
702
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -702,21 +704,16 @@ app.get('/api/v1/analytics/queue-depths', (res, req) => {
|
|
|
702
704
|
});
|
|
703
705
|
});
|
|
704
706
|
|
|
705
|
-
app.
|
|
706
|
-
const
|
|
707
|
-
const fromDateTime = query.get('fromDateTime');
|
|
708
|
-
const toDateTime = query.get('toDateTime');
|
|
709
|
-
const queue = query.get('queue');
|
|
710
|
-
const namespace = query.get('namespace');
|
|
711
|
-
const task = query.get('task');
|
|
707
|
+
app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
|
|
708
|
+
const transactionId = req.getParameter(0);
|
|
712
709
|
|
|
713
710
|
let aborted = false;
|
|
714
711
|
res.onAborted(() => {
|
|
715
712
|
aborted = true;
|
|
716
|
-
log('
|
|
713
|
+
log('Move to DLQ request aborted');
|
|
717
714
|
});
|
|
718
715
|
|
|
719
|
-
|
|
716
|
+
messagesRoutes.moveToDLQ(transactionId).then(result => {
|
|
720
717
|
if (aborted) return;
|
|
721
718
|
res.cork(() => {
|
|
722
719
|
setCorsHeaders(res);
|
|
@@ -724,7 +721,7 @@ app.get('/api/v1/analytics/throughput', (res, req) => {
|
|
|
724
721
|
});
|
|
725
722
|
}).catch(error => {
|
|
726
723
|
if (aborted) return;
|
|
727
|
-
log('
|
|
724
|
+
log('Move to DLQ error:', error);
|
|
728
725
|
res.cork(() => {
|
|
729
726
|
setCorsHeaders(res);
|
|
730
727
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -732,29 +729,24 @@ app.get('/api/v1/analytics/throughput', (res, req) => {
|
|
|
732
729
|
});
|
|
733
730
|
});
|
|
734
731
|
|
|
735
|
-
app.get('/api/v1/
|
|
736
|
-
const
|
|
737
|
-
const queue = query.get('queue');
|
|
738
|
-
const namespace = query.get('namespace');
|
|
739
|
-
const task = query.get('task');
|
|
740
|
-
const fromDateTime = query.get('fromDateTime');
|
|
741
|
-
const toDateTime = query.get('toDateTime');
|
|
732
|
+
app.get('/api/v1/messages/:transactionId/related', (res, req) => {
|
|
733
|
+
const transactionId = req.getParameter(0);
|
|
742
734
|
|
|
743
735
|
let aborted = false;
|
|
744
736
|
res.onAborted(() => {
|
|
745
737
|
aborted = true;
|
|
746
|
-
log('
|
|
738
|
+
log('Get related messages request aborted');
|
|
747
739
|
});
|
|
748
740
|
|
|
749
|
-
|
|
741
|
+
messagesRoutes.getRelatedMessages(transactionId).then(result => {
|
|
750
742
|
if (aborted) return;
|
|
751
743
|
res.cork(() => {
|
|
752
744
|
setCorsHeaders(res);
|
|
753
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
745
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ messages: result }));
|
|
754
746
|
});
|
|
755
747
|
}).catch(error => {
|
|
756
748
|
if (aborted) return;
|
|
757
|
-
log('
|
|
749
|
+
log('Get related messages error:', error);
|
|
758
750
|
res.cork(() => {
|
|
759
751
|
setCorsHeaders(res);
|
|
760
752
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -762,22 +754,18 @@ app.get('/api/v1/analytics/queue-lag', (res, req) => {
|
|
|
762
754
|
});
|
|
763
755
|
});
|
|
764
756
|
|
|
765
|
-
|
|
766
|
-
|
|
757
|
+
app.del('/api/v1/queues/:queue/clear', (res, req) => {
|
|
758
|
+
const queue = req.getParameter(0);
|
|
767
759
|
const query = new URLSearchParams(req.getQuery());
|
|
768
|
-
const
|
|
769
|
-
const namespace = query.get('namespace');
|
|
770
|
-
const task = query.get('task');
|
|
771
|
-
const fromDateTime = query.get('fromDateTime');
|
|
772
|
-
const toDateTime = query.get('toDateTime');
|
|
760
|
+
const partition = query.get('partition');
|
|
773
761
|
|
|
774
762
|
let aborted = false;
|
|
775
763
|
res.onAborted(() => {
|
|
776
764
|
aborted = true;
|
|
777
|
-
log('
|
|
765
|
+
log('Clear queue request aborted');
|
|
778
766
|
});
|
|
779
767
|
|
|
780
|
-
|
|
768
|
+
messagesRoutes.clearQueue(queue, partition).then(result => {
|
|
781
769
|
if (aborted) return;
|
|
782
770
|
res.cork(() => {
|
|
783
771
|
setCorsHeaders(res);
|
|
@@ -785,7 +773,7 @@ app.get('/api/v1/analytics/queue-stats', (res, req) => {
|
|
|
785
773
|
});
|
|
786
774
|
}).catch(error => {
|
|
787
775
|
if (aborted) return;
|
|
788
|
-
log('
|
|
776
|
+
log('Clear queue error:', error);
|
|
789
777
|
res.cork(() => {
|
|
790
778
|
setCorsHeaders(res);
|
|
791
779
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -793,34 +781,27 @@ app.get('/api/v1/analytics/queue-stats', (res, req) => {
|
|
|
793
781
|
});
|
|
794
782
|
});
|
|
795
783
|
|
|
796
|
-
//
|
|
797
|
-
app.get('/api/v1/
|
|
784
|
+
// Resources routes
|
|
785
|
+
app.get('/api/v1/resources/queues', (res, req) => {
|
|
798
786
|
const query = new URLSearchParams(req.getQuery());
|
|
787
|
+
const namespace = query.get('namespace');
|
|
788
|
+
const task = query.get('task');
|
|
799
789
|
|
|
800
790
|
let aborted = false;
|
|
801
791
|
res.onAborted(() => {
|
|
802
792
|
aborted = true;
|
|
803
|
-
log('
|
|
793
|
+
log('Get queues request aborted');
|
|
804
794
|
});
|
|
805
795
|
|
|
806
|
-
|
|
807
|
-
ns: query.get('ns'),
|
|
808
|
-
task: query.get('task'),
|
|
809
|
-
queue: query.get('queue'),
|
|
810
|
-
status: query.get('status'),
|
|
811
|
-
limit: parseInt(query.get('limit') || config.API.DEFAULT_LIMIT.toString()),
|
|
812
|
-
offset: parseInt(query.get('offset') || config.API.DEFAULT_OFFSET.toString())
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
messagesRoutes.listMessages(filters).then(result => {
|
|
796
|
+
resourcesRoutes.getQueues({ namespace, task }).then(result => {
|
|
816
797
|
if (aborted) return;
|
|
817
798
|
res.cork(() => {
|
|
818
799
|
setCorsHeaders(res);
|
|
819
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({
|
|
800
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ queues: result }));
|
|
820
801
|
});
|
|
821
802
|
}).catch(error => {
|
|
822
803
|
if (aborted) return;
|
|
823
|
-
log('
|
|
804
|
+
log('Get queues error:', error);
|
|
824
805
|
res.cork(() => {
|
|
825
806
|
setCorsHeaders(res);
|
|
826
807
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -828,16 +809,16 @@ app.get('/api/v1/messages', (res, req) => {
|
|
|
828
809
|
});
|
|
829
810
|
});
|
|
830
811
|
|
|
831
|
-
app.get('/api/v1/
|
|
832
|
-
const
|
|
812
|
+
app.get('/api/v1/resources/queues/:queue', (res, req) => {
|
|
813
|
+
const queue = req.getParameter(0);
|
|
833
814
|
|
|
834
815
|
let aborted = false;
|
|
835
816
|
res.onAborted(() => {
|
|
836
817
|
aborted = true;
|
|
837
|
-
log('Get
|
|
818
|
+
log('Get queue request aborted');
|
|
838
819
|
});
|
|
839
820
|
|
|
840
|
-
|
|
821
|
+
resourcesRoutes.getQueue(queue).then(result => {
|
|
841
822
|
if (aborted) return;
|
|
842
823
|
res.cork(() => {
|
|
843
824
|
setCorsHeaders(res);
|
|
@@ -845,59 +826,58 @@ app.get('/api/v1/messages/:transactionId', (res, req) => {
|
|
|
845
826
|
});
|
|
846
827
|
}).catch(error => {
|
|
847
828
|
if (aborted) return;
|
|
848
|
-
log('Get
|
|
829
|
+
log('Get queue error:', error);
|
|
849
830
|
res.cork(() => {
|
|
850
831
|
setCorsHeaders(res);
|
|
851
|
-
res.writeStatus(error.message === '
|
|
832
|
+
res.writeStatus(error.message === 'Queue not found' ? config.HTTP_STATUS.NOT_FOUND.toString() : config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString())
|
|
852
833
|
.end(JSON.stringify({ error: error.message }));
|
|
853
834
|
});
|
|
854
835
|
});
|
|
855
836
|
});
|
|
856
837
|
|
|
857
|
-
app.
|
|
858
|
-
const
|
|
838
|
+
app.get('/api/v1/resources/partitions', (res, req) => {
|
|
839
|
+
const query = new URLSearchParams(req.getQuery());
|
|
840
|
+
const queue = query.get('queue');
|
|
841
|
+
const minDepth = query.get('minDepth');
|
|
859
842
|
|
|
860
843
|
let aborted = false;
|
|
861
844
|
res.onAborted(() => {
|
|
862
845
|
aborted = true;
|
|
863
|
-
log('
|
|
846
|
+
log('Get partitions request aborted');
|
|
864
847
|
});
|
|
865
848
|
|
|
866
|
-
|
|
849
|
+
resourcesRoutes.getPartitions({ queue, minDepth: minDepth ? parseInt(minDepth) : null }).then(result => {
|
|
867
850
|
if (aborted) return;
|
|
868
851
|
res.cork(() => {
|
|
869
852
|
setCorsHeaders(res);
|
|
870
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
853
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ partitions: result }));
|
|
871
854
|
});
|
|
872
855
|
}).catch(error => {
|
|
873
856
|
if (aborted) return;
|
|
874
|
-
log('
|
|
857
|
+
log('Get partitions error:', error);
|
|
875
858
|
res.cork(() => {
|
|
876
859
|
setCorsHeaders(res);
|
|
877
|
-
res.writeStatus(
|
|
878
|
-
.end(JSON.stringify({ error: error.message }));
|
|
860
|
+
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
879
861
|
});
|
|
880
862
|
});
|
|
881
863
|
});
|
|
882
864
|
|
|
883
|
-
app.
|
|
884
|
-
const transactionId = req.getParameter(0);
|
|
885
|
-
|
|
865
|
+
app.get('/api/v1/resources/namespaces', (res, req) => {
|
|
886
866
|
let aborted = false;
|
|
887
867
|
res.onAborted(() => {
|
|
888
868
|
aborted = true;
|
|
889
|
-
log('
|
|
869
|
+
log('Get namespaces request aborted');
|
|
890
870
|
});
|
|
891
871
|
|
|
892
|
-
|
|
872
|
+
resourcesRoutes.getNamespaces().then(result => {
|
|
893
873
|
if (aborted) return;
|
|
894
874
|
res.cork(() => {
|
|
895
875
|
setCorsHeaders(res);
|
|
896
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
876
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ namespaces: result }));
|
|
897
877
|
});
|
|
898
878
|
}).catch(error => {
|
|
899
879
|
if (aborted) return;
|
|
900
|
-
log('
|
|
880
|
+
log('Get namespaces error:', error);
|
|
901
881
|
res.cork(() => {
|
|
902
882
|
setCorsHeaders(res);
|
|
903
883
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -905,24 +885,22 @@ app.post('/api/v1/messages/:transactionId/retry', (res, req) => {
|
|
|
905
885
|
});
|
|
906
886
|
});
|
|
907
887
|
|
|
908
|
-
app.
|
|
909
|
-
const transactionId = req.getParameter(0);
|
|
910
|
-
|
|
888
|
+
app.get('/api/v1/resources/tasks', (res, req) => {
|
|
911
889
|
let aborted = false;
|
|
912
890
|
res.onAborted(() => {
|
|
913
891
|
aborted = true;
|
|
914
|
-
log('
|
|
892
|
+
log('Get tasks request aborted');
|
|
915
893
|
});
|
|
916
894
|
|
|
917
|
-
|
|
895
|
+
resourcesRoutes.getTasks().then(result => {
|
|
918
896
|
if (aborted) return;
|
|
919
897
|
res.cork(() => {
|
|
920
898
|
setCorsHeaders(res);
|
|
921
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
899
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ tasks: result }));
|
|
922
900
|
});
|
|
923
901
|
}).catch(error => {
|
|
924
902
|
if (aborted) return;
|
|
925
|
-
log('
|
|
903
|
+
log('Get tasks error:', error);
|
|
926
904
|
res.cork(() => {
|
|
927
905
|
setCorsHeaders(res);
|
|
928
906
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -930,24 +908,22 @@ app.post('/api/v1/messages/:transactionId/dlq', (res, req) => {
|
|
|
930
908
|
});
|
|
931
909
|
});
|
|
932
910
|
|
|
933
|
-
app.get('/api/v1/
|
|
934
|
-
const transactionId = req.getParameter(0);
|
|
935
|
-
|
|
911
|
+
app.get('/api/v1/resources/overview', (res, req) => {
|
|
936
912
|
let aborted = false;
|
|
937
913
|
res.onAborted(() => {
|
|
938
914
|
aborted = true;
|
|
939
|
-
log('Get
|
|
915
|
+
log('Get overview request aborted');
|
|
940
916
|
});
|
|
941
917
|
|
|
942
|
-
|
|
918
|
+
resourcesRoutes.getSystemOverview().then(result => {
|
|
943
919
|
if (aborted) return;
|
|
944
920
|
res.cork(() => {
|
|
945
921
|
setCorsHeaders(res);
|
|
946
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(
|
|
922
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
947
923
|
});
|
|
948
924
|
}).catch(error => {
|
|
949
925
|
if (aborted) return;
|
|
950
|
-
log('Get
|
|
926
|
+
log('Get overview error:', error);
|
|
951
927
|
res.cork(() => {
|
|
952
928
|
setCorsHeaders(res);
|
|
953
929
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -955,18 +931,17 @@ app.get('/api/v1/messages/:transactionId/related', (res, req) => {
|
|
|
955
931
|
});
|
|
956
932
|
});
|
|
957
933
|
|
|
958
|
-
|
|
934
|
+
// Delete queue
|
|
935
|
+
app.del('/api/v1/resources/queues/:queue', (res, req) => {
|
|
959
936
|
const queue = req.getParameter(0);
|
|
960
|
-
const query = new URLSearchParams(req.getQuery());
|
|
961
|
-
const partition = query.get('partition');
|
|
962
937
|
|
|
963
938
|
let aborted = false;
|
|
964
939
|
res.onAborted(() => {
|
|
965
940
|
aborted = true;
|
|
966
|
-
log('
|
|
941
|
+
log('Delete queue request aborted');
|
|
967
942
|
});
|
|
968
943
|
|
|
969
|
-
|
|
944
|
+
resourcesRoutes.deleteQueue(queue).then(result => {
|
|
970
945
|
if (aborted) return;
|
|
971
946
|
res.cork(() => {
|
|
972
947
|
setCorsHeaders(res);
|
|
@@ -974,7 +949,7 @@ app.del('/api/v1/queues/:queue/clear', (res, req) => {
|
|
|
974
949
|
});
|
|
975
950
|
}).catch(error => {
|
|
976
951
|
if (aborted) return;
|
|
977
|
-
log('
|
|
952
|
+
log('Delete queue error:', error);
|
|
978
953
|
res.cork(() => {
|
|
979
954
|
setCorsHeaders(res);
|
|
980
955
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -982,27 +957,37 @@ app.del('/api/v1/queues/:queue/clear', (res, req) => {
|
|
|
982
957
|
});
|
|
983
958
|
});
|
|
984
959
|
|
|
985
|
-
//
|
|
986
|
-
|
|
960
|
+
// ============================================================================
|
|
961
|
+
// Status/Dashboard Routes (New unified API)
|
|
962
|
+
// ============================================================================
|
|
963
|
+
|
|
964
|
+
// 1. Dashboard Status - GET /api/v1/status
|
|
965
|
+
app.get('/api/v1/status', (res, req) => {
|
|
987
966
|
const query = new URLSearchParams(req.getQuery());
|
|
988
|
-
const namespace = query.get('namespace');
|
|
989
|
-
const task = query.get('task');
|
|
990
967
|
|
|
991
968
|
let aborted = false;
|
|
992
969
|
res.onAborted(() => {
|
|
993
970
|
aborted = true;
|
|
994
|
-
log('
|
|
971
|
+
log('Status request aborted');
|
|
995
972
|
});
|
|
996
973
|
|
|
997
|
-
|
|
974
|
+
const filters = {
|
|
975
|
+
from: query.get('from'),
|
|
976
|
+
to: query.get('to'),
|
|
977
|
+
queue: query.get('queue'),
|
|
978
|
+
namespace: query.get('namespace'),
|
|
979
|
+
task: query.get('task')
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
statusRoutes.getStatus(filters).then(result => {
|
|
998
983
|
if (aborted) return;
|
|
999
984
|
res.cork(() => {
|
|
1000
985
|
setCorsHeaders(res);
|
|
1001
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(
|
|
986
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
1002
987
|
});
|
|
1003
988
|
}).catch(error => {
|
|
1004
989
|
if (aborted) return;
|
|
1005
|
-
log('
|
|
990
|
+
log('Status error:', error);
|
|
1006
991
|
res.cork(() => {
|
|
1007
992
|
setCorsHeaders(res);
|
|
1008
993
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -1010,16 +995,26 @@ app.get('/api/v1/resources/queues', (res, req) => {
|
|
|
1010
995
|
});
|
|
1011
996
|
});
|
|
1012
997
|
|
|
1013
|
-
|
|
1014
|
-
|
|
998
|
+
// 2. Queues List - GET /api/v1/status/queues
|
|
999
|
+
app.get('/api/v1/status/queues', (res, req) => {
|
|
1000
|
+
const query = new URLSearchParams(req.getQuery());
|
|
1015
1001
|
|
|
1016
1002
|
let aborted = false;
|
|
1017
1003
|
res.onAborted(() => {
|
|
1018
1004
|
aborted = true;
|
|
1019
|
-
log('
|
|
1005
|
+
log('Status queues request aborted');
|
|
1020
1006
|
});
|
|
1021
1007
|
|
|
1022
|
-
|
|
1008
|
+
const filters = {
|
|
1009
|
+
from: query.get('from'),
|
|
1010
|
+
to: query.get('to'),
|
|
1011
|
+
namespace: query.get('namespace'),
|
|
1012
|
+
task: query.get('task'),
|
|
1013
|
+
limit: query.get('limit'),
|
|
1014
|
+
offset: query.get('offset')
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
statusRoutes.getQueues(filters).then(result => {
|
|
1023
1018
|
if (aborted) return;
|
|
1024
1019
|
res.cork(() => {
|
|
1025
1020
|
setCorsHeaders(res);
|
|
@@ -1027,81 +1022,78 @@ app.get('/api/v1/resources/queues/:queue', (res, req) => {
|
|
|
1027
1022
|
});
|
|
1028
1023
|
}).catch(error => {
|
|
1029
1024
|
if (aborted) return;
|
|
1030
|
-
log('
|
|
1025
|
+
log('Status queues error:', error);
|
|
1031
1026
|
res.cork(() => {
|
|
1032
1027
|
setCorsHeaders(res);
|
|
1033
|
-
res.writeStatus(
|
|
1034
|
-
.end(JSON.stringify({ error: error.message }));
|
|
1028
|
+
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
1035
1029
|
});
|
|
1036
1030
|
});
|
|
1037
1031
|
});
|
|
1038
1032
|
|
|
1039
|
-
|
|
1033
|
+
// 3. Queue Detail - GET /api/v1/status/queues/:queueName
|
|
1034
|
+
app.get('/api/v1/status/queues/:queue', (res, req) => {
|
|
1035
|
+
const queueName = req.getParameter(0);
|
|
1040
1036
|
const query = new URLSearchParams(req.getQuery());
|
|
1041
|
-
const queue = query.get('queue');
|
|
1042
|
-
const minDepth = query.get('minDepth');
|
|
1043
1037
|
|
|
1044
1038
|
let aborted = false;
|
|
1045
1039
|
res.onAborted(() => {
|
|
1046
1040
|
aborted = true;
|
|
1047
|
-
log('
|
|
1041
|
+
log('Status queue detail request aborted');
|
|
1048
1042
|
});
|
|
1049
1043
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify({ partitions: result }));
|
|
1055
|
-
});
|
|
1056
|
-
}).catch(error => {
|
|
1057
|
-
if (aborted) return;
|
|
1058
|
-
log('Get partitions error:', error);
|
|
1059
|
-
res.cork(() => {
|
|
1060
|
-
setCorsHeaders(res);
|
|
1061
|
-
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
1062
|
-
});
|
|
1063
|
-
});
|
|
1064
|
-
});
|
|
1065
|
-
|
|
1066
|
-
app.get('/api/v1/resources/namespaces', (res, req) => {
|
|
1067
|
-
let aborted = false;
|
|
1068
|
-
res.onAborted(() => {
|
|
1069
|
-
aborted = true;
|
|
1070
|
-
log('Get namespaces request aborted');
|
|
1071
|
-
});
|
|
1044
|
+
const filters = {
|
|
1045
|
+
from: query.get('from'),
|
|
1046
|
+
to: query.get('to')
|
|
1047
|
+
};
|
|
1072
1048
|
|
|
1073
|
-
|
|
1049
|
+
statusRoutes.getQueueDetail(queueName, filters).then(result => {
|
|
1074
1050
|
if (aborted) return;
|
|
1075
1051
|
res.cork(() => {
|
|
1076
1052
|
setCorsHeaders(res);
|
|
1077
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(
|
|
1053
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
1078
1054
|
});
|
|
1079
1055
|
}).catch(error => {
|
|
1080
1056
|
if (aborted) return;
|
|
1081
|
-
log('
|
|
1057
|
+
log('Status queue detail error:', error);
|
|
1058
|
+
const statusCode = error.message === 'Queue not found'
|
|
1059
|
+
? config.HTTP_STATUS.NOT_FOUND
|
|
1060
|
+
: config.HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
1082
1061
|
res.cork(() => {
|
|
1083
1062
|
setCorsHeaders(res);
|
|
1084
|
-
res.writeStatus(
|
|
1063
|
+
res.writeStatus(statusCode.toString()).end(JSON.stringify({ error: error.message }));
|
|
1085
1064
|
});
|
|
1086
1065
|
});
|
|
1087
1066
|
});
|
|
1088
1067
|
|
|
1089
|
-
|
|
1068
|
+
// 4. Queue Messages - GET /api/v1/status/queues/:queueName/messages
|
|
1069
|
+
app.get('/api/v1/status/queues/:queue/messages', (res, req) => {
|
|
1070
|
+
const queueName = req.getParameter(0);
|
|
1071
|
+
const query = new URLSearchParams(req.getQuery());
|
|
1072
|
+
|
|
1090
1073
|
let aborted = false;
|
|
1091
1074
|
res.onAborted(() => {
|
|
1092
1075
|
aborted = true;
|
|
1093
|
-
log('
|
|
1076
|
+
log('Status queue messages request aborted');
|
|
1094
1077
|
});
|
|
1095
1078
|
|
|
1096
|
-
|
|
1079
|
+
const filters = {
|
|
1080
|
+
status: query.get('status'),
|
|
1081
|
+
partition: query.get('partition'),
|
|
1082
|
+
from: query.get('from'),
|
|
1083
|
+
to: query.get('to'),
|
|
1084
|
+
limit: query.get('limit'),
|
|
1085
|
+
offset: query.get('offset')
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
statusRoutes.getQueueMessages(queueName, filters).then(result => {
|
|
1097
1089
|
if (aborted) return;
|
|
1098
1090
|
res.cork(() => {
|
|
1099
1091
|
setCorsHeaders(res);
|
|
1100
|
-
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(
|
|
1092
|
+
res.writeStatus(config.HTTP_STATUS.OK.toString()).end(JSON.stringify(result));
|
|
1101
1093
|
});
|
|
1102
1094
|
}).catch(error => {
|
|
1103
1095
|
if (aborted) return;
|
|
1104
|
-
log('
|
|
1096
|
+
log('Status queue messages error:', error);
|
|
1105
1097
|
res.cork(() => {
|
|
1106
1098
|
setCorsHeaders(res);
|
|
1107
1099
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -1109,14 +1101,26 @@ app.get('/api/v1/resources/tasks', (res, req) => {
|
|
|
1109
1101
|
});
|
|
1110
1102
|
});
|
|
1111
1103
|
|
|
1112
|
-
|
|
1104
|
+
// 5. Analytics - GET /api/v1/status/analytics
|
|
1105
|
+
app.get('/api/v1/status/analytics', (res, req) => {
|
|
1106
|
+
const query = new URLSearchParams(req.getQuery());
|
|
1107
|
+
|
|
1113
1108
|
let aborted = false;
|
|
1114
1109
|
res.onAborted(() => {
|
|
1115
1110
|
aborted = true;
|
|
1116
|
-
log('
|
|
1111
|
+
log('Status analytics request aborted');
|
|
1117
1112
|
});
|
|
1118
1113
|
|
|
1119
|
-
|
|
1114
|
+
const filters = {
|
|
1115
|
+
from: query.get('from'),
|
|
1116
|
+
to: query.get('to'),
|
|
1117
|
+
queue: query.get('queue'),
|
|
1118
|
+
namespace: query.get('namespace'),
|
|
1119
|
+
task: query.get('task'),
|
|
1120
|
+
interval: query.get('interval') || 'hour'
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
statusRoutes.getAnalytics(filters).then(result => {
|
|
1120
1124
|
if (aborted) return;
|
|
1121
1125
|
res.cork(() => {
|
|
1122
1126
|
setCorsHeaders(res);
|
|
@@ -1124,7 +1128,7 @@ app.get('/api/v1/resources/overview', (res, req) => {
|
|
|
1124
1128
|
});
|
|
1125
1129
|
}).catch(error => {
|
|
1126
1130
|
if (aborted) return;
|
|
1127
|
-
log('
|
|
1131
|
+
log('Status analytics error:', error);
|
|
1128
1132
|
res.cork(() => {
|
|
1129
1133
|
setCorsHeaders(res);
|
|
1130
1134
|
res.writeStatus(config.HTTP_STATUS.INTERNAL_SERVER_ERROR.toString()).end(JSON.stringify({ error: error.message }));
|
|
@@ -1217,38 +1221,41 @@ app.listen(HOST, PORT, async (token) => {
|
|
|
1217
1221
|
log(`ā
Queen Server running at http://${HOST}:${PORT}`);
|
|
1218
1222
|
log(`š Features enabled:`);
|
|
1219
1223
|
log(` - Encryption: ${encryptionEnabled ? 'ā
' : 'ā (set QUEEN_ENCRYPTION_KEY)'}`);
|
|
1224
|
+
log(` - System Events: ${config.SYSTEM_EVENTS.ENABLED ? 'ā
' : 'ā (disabled via config)'}`);
|
|
1220
1225
|
log(` - Retention: ā
`);
|
|
1221
1226
|
log(` - Eviction: ā
`);
|
|
1222
1227
|
log(` - WebSocket Dashboard: ws://${HOST}:${PORT}/ws/dashboard`);
|
|
1223
1228
|
log(` - Server Instance ID: ${SERVER_INSTANCE_ID}`);
|
|
1224
1229
|
|
|
1225
|
-
// Start system event synchronization and consumption
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1230
|
+
// Start system event synchronization and consumption (only if enabled)
|
|
1231
|
+
if (config.SYSTEM_EVENTS.ENABLED) {
|
|
1232
|
+
try {
|
|
1233
|
+
// Create temporary client for synchronization
|
|
1234
|
+
const syncClient = createQueenClient({
|
|
1235
|
+
baseUrl: `http://localhost:${PORT}`
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
// Synchronize system events (catch up on missed events)
|
|
1239
|
+
await syncSystemEvents(syncClient, systemEventManager, SERVER_INSTANCE_ID);
|
|
1240
|
+
|
|
1241
|
+
// Start consuming system events
|
|
1242
|
+
// Note: consume() returns the stop function directly
|
|
1243
|
+
stopSystemEventConsumer = syncClient.consume({
|
|
1244
|
+
queue: SYSTEM_QUEUE,
|
|
1245
|
+
consumerGroup: SERVER_INSTANCE_ID,
|
|
1246
|
+
handler: async (message) => {
|
|
1247
|
+
await systemEventManager.processSystemEvent(message.payload);
|
|
1248
|
+
},
|
|
1249
|
+
options: {
|
|
1250
|
+
batch: 10,
|
|
1251
|
+
wait: true
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
log(`ā
System event consumer started`);
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
log(`ā ļø Failed to start system event consumer: ${error.message}`);
|
|
1258
|
+
}
|
|
1252
1259
|
}
|
|
1253
1260
|
|
|
1254
1261
|
log(`šÆ Ready to process messages with high performance!`);
|
|
@@ -1262,6 +1269,9 @@ app.listen(HOST, PORT, async (token) => {
|
|
|
1262
1269
|
const shutdown = async (signal) => {
|
|
1263
1270
|
log(`\nš Received ${signal}, shutting down gracefully...`);
|
|
1264
1271
|
|
|
1272
|
+
// Set shutdown flag immediately to prevent new operations
|
|
1273
|
+
isShuttingDown = true;
|
|
1274
|
+
|
|
1265
1275
|
// Stop system event consumer
|
|
1266
1276
|
if (stopSystemEventConsumer) {
|
|
1267
1277
|
stopSystemEventConsumer();
|
|
@@ -1269,8 +1279,31 @@ const shutdown = async (signal) => {
|
|
|
1269
1279
|
}
|
|
1270
1280
|
|
|
1271
1281
|
// Stop background jobs
|
|
1272
|
-
if (stopRetention)
|
|
1273
|
-
|
|
1282
|
+
if (stopRetention) {
|
|
1283
|
+
stopRetention();
|
|
1284
|
+
log('ā
Retention job stopped');
|
|
1285
|
+
}
|
|
1286
|
+
if (stopEviction) {
|
|
1287
|
+
stopEviction();
|
|
1288
|
+
log('ā
Eviction job stopped');
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// Clear background intervals
|
|
1292
|
+
if (leaseReclaimInterval) {
|
|
1293
|
+
clearInterval(leaseReclaimInterval);
|
|
1294
|
+
log('ā
Lease reclaim interval stopped');
|
|
1295
|
+
}
|
|
1296
|
+
if (queueDepthInterval) {
|
|
1297
|
+
clearInterval(queueDepthInterval);
|
|
1298
|
+
log('ā
Queue depth update interval stopped');
|
|
1299
|
+
}
|
|
1300
|
+
if (systemStatsInterval) {
|
|
1301
|
+
clearInterval(systemStatsInterval);
|
|
1302
|
+
log('ā
System stats interval stopped');
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Wait a moment for any in-flight operations to complete
|
|
1306
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1274
1307
|
|
|
1275
1308
|
const uptime = (Date.now() - startTime) / 1000;
|
|
1276
1309
|
if (messageCount > 0) {
|
|
@@ -1278,7 +1311,10 @@ const shutdown = async (signal) => {
|
|
|
1278
1311
|
log(` Average: ${(messageCount / uptime).toFixed(2)} messages/second`);
|
|
1279
1312
|
}
|
|
1280
1313
|
|
|
1314
|
+
// Close database pool last
|
|
1281
1315
|
await pool.end();
|
|
1316
|
+
log('ā
Database pool closed');
|
|
1317
|
+
|
|
1282
1318
|
process.exit(0);
|
|
1283
1319
|
};
|
|
1284
1320
|
|