queen-mq 0.1.1 → 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.
Files changed (139) hide show
  1. package/API.md +1226 -0
  2. package/AUTH.md +2044 -0
  3. package/LICENSE.md +202 -0
  4. package/README.md +276 -53
  5. package/WEBAPP.md +1889 -0
  6. package/assets/dashboard-01.png +0 -0
  7. package/assets/queen-logo-blue.svg +210 -0
  8. package/assets/queen-logo-cyan.svg +210 -0
  9. package/assets/queen-logo-indigo.svg +210 -0
  10. package/assets/queen-logo-orange.svg +210 -0
  11. package/assets/queen-logo-pink.svg +210 -0
  12. package/assets/queen-logo-purple.svg +210 -0
  13. package/assets/queen-logo-rose.svg +239 -0
  14. package/assets/queen-logo.svg +263 -0
  15. package/examples/batch-processing.js +58 -0
  16. package/examples/test-dashboard-api.js +200 -0
  17. package/package.json +4 -2
  18. package/server.log +1 -0
  19. package/src/benchmark/consumer.js +207 -0
  20. package/src/benchmark/consumer_multi.js +216 -0
  21. package/src/benchmark/producer.js +75 -0
  22. package/src/benchmark/producer_multi.js +115 -0
  23. package/src/client/client.js +192 -16
  24. package/src/client/queenClient.js +5 -0
  25. package/src/cluster-server.js +242 -0
  26. package/src/config.js +19 -5
  27. package/src/database/connection.js +42 -16
  28. package/src/database/poolManager.js +7 -0
  29. package/src/database/schema-v2.sql +194 -130
  30. package/src/managers/queueManagerOptimized.js +823 -933
  31. package/src/managers/systemEventManager.js +8 -3
  32. package/src/routes/messages.js +127 -57
  33. package/src/routes/pop.js +27 -43
  34. package/src/routes/resources.js +61 -27
  35. package/src/routes/status.js +1037 -0
  36. package/src/server.js +308 -272
  37. package/src/services/evictionService.js +57 -28
  38. package/src/services/retentionService.js +44 -11
  39. package/src/test/advanced-pattern-tests.js +5 -5
  40. package/src/test/bus-mode-tests.js +24 -11
  41. package/src/test/edge-case-tests.js +12 -5
  42. package/src/test/enterprise-tests.js +48 -15
  43. package/src/test/utils.js +1 -1
  44. package/src/utils/streaming.js +231 -0
  45. package/src/utils/uuid.js +2 -2
  46. package/src/websocket/wsServer.js +10 -3
  47. package/test-keepalive-v2.sh +22 -0
  48. package/webapp/COLOR_GUIDE.md +118 -0
  49. package/webapp/README.md +143 -0
  50. package/webapp/index.html +14 -0
  51. package/webapp/package-lock.json +3184 -0
  52. package/webapp/package.json +25 -0
  53. package/webapp/postcss.config.js +7 -0
  54. package/webapp/public/assets/queen-logo-blue.svg +210 -0
  55. package/webapp/public/assets/queen-logo-cyan.svg +210 -0
  56. package/webapp/public/assets/queen-logo-indigo.svg +210 -0
  57. package/webapp/public/assets/queen-logo-orange.svg +210 -0
  58. package/webapp/public/assets/queen-logo-pink.svg +210 -0
  59. package/webapp/public/assets/queen-logo-purple.svg +210 -0
  60. package/webapp/public/assets/queen-logo-rose.svg +239 -0
  61. package/webapp/public/assets/queen-logo.svg +263 -0
  62. package/webapp/src/App.vue +19 -0
  63. package/webapp/src/api/analytics.js +10 -0
  64. package/webapp/src/api/client.js +29 -0
  65. package/webapp/src/api/consumers.js +52 -0
  66. package/webapp/src/api/health.js +7 -0
  67. package/webapp/src/api/messages.js +26 -0
  68. package/webapp/src/api/queues.js +14 -0
  69. package/webapp/src/api/resources.js +8 -0
  70. package/webapp/src/assets/styles/main.css +357 -0
  71. package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
  72. package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
  73. package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
  74. package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
  75. package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
  76. package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
  77. package/webapp/src/components/common/ConfirmDialog.vue +56 -0
  78. package/webapp/src/components/common/LoadingSpinner.vue +6 -0
  79. package/webapp/src/components/common/MetricCard.vue +43 -0
  80. package/webapp/src/components/common/StatusBadge.vue +45 -0
  81. package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
  82. package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
  83. package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
  84. package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
  85. package/webapp/src/components/layout/AppLayout.vue +110 -0
  86. package/webapp/src/components/layout/AppSidebar.vue +304 -0
  87. package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
  88. package/webapp/src/components/messages/MessageFilters.vue +114 -0
  89. package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
  90. package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
  91. package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
  92. package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
  93. package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
  94. package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
  95. package/webapp/src/components/queues/QueueFilters.vue +90 -0
  96. package/webapp/src/composables/useApi.js +34 -0
  97. package/webapp/src/composables/useTheme.js +36 -0
  98. package/webapp/src/main.js +11 -0
  99. package/webapp/src/router/index.js +42 -0
  100. package/webapp/src/utils/colors.js +96 -0
  101. package/webapp/src/utils/formatters.js +49 -0
  102. package/webapp/src/views/Analytics.vue +377 -0
  103. package/webapp/src/views/ConsumerGroups.vue +433 -0
  104. package/webapp/src/views/Dashboard.vue +418 -0
  105. package/webapp/src/views/Messages.vue +361 -0
  106. package/webapp/src/views/QueueDetail.vue +582 -0
  107. package/webapp/src/views/Queues.vue +496 -0
  108. package/webapp/tailwind.config.js +25 -0
  109. package/webapp/vite.config.js +10 -0
  110. package/dashboard/.vscode/extensions.json +0 -3
  111. package/dashboard/README.md +0 -5
  112. package/dashboard/index.html +0 -14
  113. package/dashboard/package-lock.json +0 -1458
  114. package/dashboard/package.json +0 -25
  115. package/dashboard/public/vite.svg +0 -1
  116. package/dashboard/src/App.vue +0 -29
  117. package/dashboard/src/assets/styles/main.css +0 -908
  118. package/dashboard/src/assets/vue.svg +0 -1
  119. package/dashboard/src/components/cards/MetricCard.vue +0 -298
  120. package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
  121. package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
  122. package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
  123. package/dashboard/src/components/common/ActivityFeed.vue +0 -251
  124. package/dashboard/src/components/layout/AppHeader.vue +0 -208
  125. package/dashboard/src/components/layout/AppLayout.vue +0 -88
  126. package/dashboard/src/components/layout/AppSidebar.vue +0 -261
  127. package/dashboard/src/main.js +0 -44
  128. package/dashboard/src/router.js +0 -54
  129. package/dashboard/src/services/api.js +0 -187
  130. package/dashboard/src/services/websocket.js +0 -167
  131. package/dashboard/src/utils/constants.js +0 -56
  132. package/dashboard/src/utils/helpers.js +0 -118
  133. package/dashboard/src/views/Analytics.vue +0 -912
  134. package/dashboard/src/views/Dashboard.vue +0 -906
  135. package/dashboard/src/views/Messages.vue +0 -437
  136. package/dashboard/src/views/QueueDetail.vue +0 -501
  137. package/dashboard/src/views/Queues.vue +0 -333
  138. package/dashboard/vite.config.js +0 -30
  139. package/src/routes/analytics.js +0 -812
@@ -0,0 +1,357 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
2
+
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
6
+
7
+ @layer base {
8
+ * {
9
+ @apply border-gray-200 dark:border-gray-700;
10
+ }
11
+
12
+ body {
13
+ @apply bg-gray-100 dark:bg-[#0d1117] text-gray-900 dark:text-gray-100 text-sm antialiased;
14
+ }
15
+
16
+ h1 {
17
+ @apply text-xl font-semibold tracking-tight;
18
+ }
19
+
20
+ h2 {
21
+ @apply text-lg font-semibold tracking-tight;
22
+ }
23
+
24
+ h3 {
25
+ @apply text-base font-semibold tracking-tight;
26
+ }
27
+ }
28
+
29
+ @layer components {
30
+ .card {
31
+ @apply bg-white dark:bg-slate-800 rounded-xl shadow-sm border p-4 sm:p-5
32
+ transition-all duration-200 hover:shadow-md;
33
+ background: linear-gradient(to bottom, white, white) padding-box,
34
+ linear-gradient(to bottom, transparent, transparent) border-box;
35
+ }
36
+
37
+ .dark .card {
38
+ background: linear-gradient(to bottom, rgb(30, 41, 59), rgb(30, 41, 59)) padding-box,
39
+ linear-gradient(to bottom, rgba(248, 113, 113, 0.1), rgba(168, 85, 247, 0.1)) border-box;
40
+ }
41
+
42
+ /* Metric cards with top accent */
43
+ .metric-card {
44
+ @apply card relative;
45
+ }
46
+
47
+ .metric-card:hover {
48
+ @apply shadow-lg;
49
+ transform: translateY(-2px);
50
+ }
51
+
52
+ .btn {
53
+ @apply px-4 py-2 rounded-lg font-medium transition-all duration-200 text-sm
54
+ inline-flex items-center justify-center gap-2;
55
+ }
56
+
57
+ .btn:active {
58
+ @apply scale-95;
59
+ }
60
+
61
+ .btn-primary {
62
+ @apply bg-gradient-to-br from-rose-600 to-rose-700 hover:from-rose-700 hover:to-rose-800
63
+ active:from-rose-800 active:to-rose-900 text-white shadow-sm hover:shadow-md
64
+ disabled:opacity-50 disabled:cursor-not-allowed;
65
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05), 0 0 0 1px rgb(244 63 94 / 0.1);
66
+ }
67
+
68
+ .btn-primary:hover {
69
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1),
70
+ 0 0 0 1px rgb(244 63 94 / 0.2);
71
+ }
72
+
73
+ .btn-secondary {
74
+ @apply bg-gray-100 hover:bg-gray-200 active:bg-gray-300
75
+ dark:bg-slate-700 dark:hover:bg-slate-600 dark:active:bg-slate-500
76
+ text-gray-900 dark:text-gray-100 disabled:opacity-50;
77
+ }
78
+
79
+ .btn-danger {
80
+ @apply bg-gradient-to-br from-red-600 to-red-700 hover:from-red-700 hover:to-red-800
81
+ text-white shadow-sm hover:shadow-md disabled:opacity-50;
82
+ }
83
+
84
+ .btn-ghost {
85
+ @apply hover:bg-gray-100 active:bg-gray-200 dark:hover:bg-slate-700
86
+ dark:active:bg-slate-600 text-gray-700 dark:text-gray-300;
87
+ }
88
+
89
+ .badge {
90
+ @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium;
91
+ transition: all 0.2s ease;
92
+ }
93
+
94
+ .badge-success {
95
+ @apply bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400
96
+ ring-1 ring-green-600/20 dark:ring-green-400/20;
97
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
98
+ }
99
+
100
+ .badge-success:hover {
101
+ box-shadow: 0 0 8px 0 rgba(16, 185, 129, 0.3);
102
+ }
103
+
104
+ .badge-warning {
105
+ @apply bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400
106
+ ring-1 ring-yellow-600/20 dark:ring-yellow-400/20;
107
+ box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
108
+ }
109
+
110
+ .badge-warning:hover {
111
+ box-shadow: 0 0 8px 0 rgba(245, 158, 11, 0.3);
112
+ }
113
+
114
+ .badge-danger {
115
+ @apply bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400
116
+ ring-1 ring-red-600/20 dark:ring-red-400/20;
117
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
118
+ }
119
+
120
+ .badge-danger:hover {
121
+ box-shadow: 0 0 8px 0 rgba(239, 68, 68, 0.3);
122
+ }
123
+
124
+ .badge-info {
125
+ @apply bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400
126
+ ring-1 ring-purple-600/20 dark:ring-purple-400/20;
127
+ box-shadow: 0 0 0 0 rgba(168, 85, 247, 0);
128
+ }
129
+
130
+ .badge-info:hover {
131
+ box-shadow: 0 0 8px 0 rgba(168, 85, 247, 0.3);
132
+ }
133
+
134
+ .input {
135
+ @apply w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-slate-800
136
+ focus:outline-none focus:ring-2 focus:ring-rose-500 focus:border-transparent
137
+ transition-all duration-200;
138
+ }
139
+
140
+ .input:focus {
141
+ box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.1);
142
+ }
143
+
144
+ /* Table styles */
145
+ .table-container {
146
+ @apply overflow-x-auto -mx-4 sm:mx-0 rounded-lg;
147
+ }
148
+
149
+ .table {
150
+ @apply w-full text-sm;
151
+ }
152
+
153
+ .table thead {
154
+ @apply text-xs text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700;
155
+ background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.02));
156
+ }
157
+
158
+ .dark .table thead {
159
+ background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.02));
160
+ }
161
+
162
+ .table th {
163
+ @apply pb-3 font-semibold text-left px-4 sm:px-3 first:pl-4 last:pr-4
164
+ uppercase tracking-wider;
165
+ }
166
+
167
+ .table tbody tr {
168
+ @apply border-b border-gray-100 dark:border-gray-700/50 last:border-b-0
169
+ transition-all duration-150;
170
+ }
171
+
172
+ .table tbody tr:nth-child(even) {
173
+ @apply bg-gray-50/50 dark:bg-slate-700/20;
174
+ }
175
+
176
+ .table tbody tr:hover {
177
+ @apply bg-gradient-to-r from-rose-50/50 to-purple-50/50
178
+ dark:from-rose-900/10 dark:to-purple-900/10;
179
+ box-shadow: inset 3px 0 0 0 rgb(244, 63, 94, 0.5);
180
+ }
181
+
182
+ .table td {
183
+ @apply py-3.5 px-4 sm:px-3 first:pl-4 last:pr-4;
184
+ }
185
+
186
+ /* Metric card specific */
187
+ .metric-value {
188
+ @apply text-2xl sm:text-3xl font-bold mt-1.5;
189
+ background: linear-gradient(135deg, #f43f5e 0%, #ec4899 50%, #a855f7 100%);
190
+ -webkit-background-clip: text;
191
+ -webkit-text-fill-color: transparent;
192
+ background-clip: text;
193
+ letter-spacing: -0.02em;
194
+ }
195
+
196
+ .metric-label {
197
+ @apply text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider font-semibold;
198
+ }
199
+
200
+ /* Chart container */
201
+ .chart-container {
202
+ @apply h-64 sm:h-72 md:h-80 w-full relative;
203
+ }
204
+
205
+ /* Status indicators */
206
+ .status-dot {
207
+ @apply w-2 h-2 rounded-full;
208
+ animation: pulse-glow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
209
+ }
210
+
211
+ .status-dot.healthy {
212
+ @apply bg-green-500;
213
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
214
+ }
215
+
216
+ .status-dot.unhealthy {
217
+ @apply bg-red-500;
218
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
219
+ }
220
+
221
+ @keyframes pulse-glow {
222
+ 0%, 100% {
223
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
224
+ }
225
+ 50% {
226
+ box-shadow: 0 0 0 4px rgba(16, 185, 129, 0);
227
+ }
228
+ }
229
+ }
230
+
231
+ /* Scrollbar styling */
232
+ @layer utilities {
233
+ .scrollbar-thin::-webkit-scrollbar {
234
+ width: 6px;
235
+ height: 6px;
236
+ }
237
+
238
+ .scrollbar-thin::-webkit-scrollbar-track {
239
+ @apply bg-gray-100 dark:bg-slate-800 rounded-full;
240
+ }
241
+
242
+ .scrollbar-thin::-webkit-scrollbar-thumb {
243
+ @apply bg-gradient-to-b from-rose-400 to-purple-400 rounded-full;
244
+ }
245
+
246
+ .scrollbar-thin::-webkit-scrollbar-thumb:hover {
247
+ @apply from-rose-500 to-purple-500;
248
+ }
249
+
250
+ /* Custom utilities */
251
+ .text-balance {
252
+ text-wrap: balance;
253
+ }
254
+
255
+ /* Smooth scrolling */
256
+ .smooth-scroll {
257
+ scroll-behavior: smooth;
258
+ }
259
+
260
+ /* Glassmorphism */
261
+ .glass {
262
+ background: rgba(255, 255, 255, 0.8);
263
+ backdrop-filter: blur(10px);
264
+ -webkit-backdrop-filter: blur(10px);
265
+ }
266
+
267
+ .dark .glass {
268
+ background: rgba(30, 41, 59, 0.8);
269
+ }
270
+ }
271
+
272
+ /* Custom animations */
273
+ @keyframes fadeIn {
274
+ from {
275
+ opacity: 0;
276
+ transform: translateY(10px);
277
+ }
278
+ to {
279
+ opacity: 1;
280
+ transform: translateY(0);
281
+ }
282
+ }
283
+
284
+ @keyframes slideIn {
285
+ from {
286
+ opacity: 0;
287
+ transform: translateX(-10px);
288
+ }
289
+ to {
290
+ opacity: 1;
291
+ transform: translateX(0);
292
+ }
293
+ }
294
+
295
+ @keyframes scaleIn {
296
+ from {
297
+ opacity: 0;
298
+ transform: scale(0.95);
299
+ }
300
+ to {
301
+ opacity: 1;
302
+ transform: scale(1);
303
+ }
304
+ }
305
+
306
+ .animate-fade-in {
307
+ animation: fadeIn 0.3s ease-out;
308
+ }
309
+
310
+ .animate-slide-in {
311
+ animation: slideIn 0.3s ease-out;
312
+ }
313
+
314
+ .animate-scale-in {
315
+ animation: scaleIn 0.2s ease-out;
316
+ }
317
+
318
+ /* Focus visible for accessibility */
319
+ :focus-visible {
320
+ @apply outline-2 outline-offset-2 outline-rose-500 rounded;
321
+ }
322
+
323
+ /* Selection color */
324
+ ::selection {
325
+ @apply bg-rose-200 dark:bg-rose-800/50 text-gray-900 dark:text-gray-100;
326
+ }
327
+
328
+ /* Gradient text utility */
329
+ .gradient-text {
330
+ background: linear-gradient(135deg, #f43f5e 0%, #ec4899 50%, #a855f7 100%);
331
+ -webkit-background-clip: text;
332
+ -webkit-text-fill-color: transparent;
333
+ background-clip: text;
334
+ }
335
+
336
+ /* Card with gradient border */
337
+ .card-gradient-border {
338
+ position: relative;
339
+ background: white;
340
+ border-radius: 0.75rem;
341
+ padding: 1px;
342
+ background: linear-gradient(135deg, rgba(244, 63, 94, 0.2), rgba(168, 85, 247, 0.2));
343
+ }
344
+
345
+ .dark .card-gradient-border {
346
+ background: linear-gradient(135deg, rgba(244, 63, 94, 0.3), rgba(168, 85, 247, 0.3));
347
+ }
348
+
349
+ .card-gradient-border > * {
350
+ background: white;
351
+ border-radius: 0.7rem;
352
+ padding: 1rem 1.25rem;
353
+ }
354
+
355
+ .dark .card-gradient-border > * {
356
+ background: rgb(30, 41, 59);
357
+ }
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <div>
3
+ <div class="flex flex-col sm:flex-row gap-3">
4
+ <!-- Time Range -->
5
+ <div class="flex items-center gap-2">
6
+ <label class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">Time Range:</label>
7
+ <select
8
+ :value="timeRange"
9
+ @change="$emit('update:timeRange', $event.target.value)"
10
+ class="input w-32"
11
+ >
12
+ <option value="1h">Last Hour</option>
13
+ <option value="6h">6 Hours</option>
14
+ <option value="24h">24 Hours</option>
15
+ <option value="7d">7 Days</option>
16
+ </select>
17
+ </div>
18
+
19
+ <!-- Queue Filter -->
20
+ <select
21
+ :value="queue"
22
+ @change="$emit('update:queue', $event.target.value)"
23
+ class="input flex-1 sm:w-48"
24
+ >
25
+ <option value="">All Queues</option>
26
+ <option v-for="q in queues" :key="q.id" :value="q.name">
27
+ {{ q.name }}
28
+ </option>
29
+ </select>
30
+
31
+ <!-- Namespace Filter -->
32
+ <select
33
+ :value="namespace"
34
+ @change="$emit('update:namespace', $event.target.value)"
35
+ class="input sm:w-40"
36
+ >
37
+ <option value="">All Namespaces</option>
38
+ <option v-for="ns in namespaces" :key="ns.namespace" :value="ns.namespace">
39
+ {{ ns.namespace }}
40
+ </option>
41
+ </select>
42
+
43
+ <!-- Task Filter -->
44
+ <select
45
+ :value="task"
46
+ @change="$emit('update:task', $event.target.value)"
47
+ class="input sm:w-40"
48
+ >
49
+ <option value="">All Tasks</option>
50
+ <option v-for="t in tasks" :key="t.task" :value="t.task">
51
+ {{ t.task }}
52
+ </option>
53
+ </select>
54
+
55
+ <!-- Clear Filters -->
56
+ <button
57
+ v-if="queue || namespace || task"
58
+ @click="clearFilters"
59
+ class="btn btn-secondary whitespace-nowrap"
60
+ >
61
+ Clear Filters
62
+ </button>
63
+ </div>
64
+ </div>
65
+ </template>
66
+
67
+
68
+ <script setup>
69
+ defineProps({
70
+ timeRange: String,
71
+ queue: String,
72
+ namespace: String,
73
+ task: String,
74
+ queues: Array,
75
+ namespaces: Array,
76
+ tasks: Array,
77
+ });
78
+
79
+ const emit = defineEmits(['update:timeRange', 'update:queue', 'update:namespace', 'update:task']);
80
+
81
+ function clearFilters() {
82
+ emit('update:queue', '');
83
+ emit('update:namespace', '');
84
+ emit('update:task', '');
85
+ }
86
+ </script>
87
+
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
3
+ <div class="card">
4
+ <p class="metric-label">Avg Throughput</p>
5
+ <p class="metric-value">{{ avgThroughput }}</p>
6
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-1.5">messages/sec</p>
7
+ </div>
8
+
9
+ <div class="card">
10
+ <p class="metric-label">Total Processed</p>
11
+ <p class="metric-value">{{ formatNumber(totalProcessed) }}</p>
12
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-1.5">in time range</p>
13
+ </div>
14
+
15
+ <div class="card">
16
+ <p class="metric-label">Error Rate</p>
17
+ <p class="metric-value">{{ errorRate }}%</p>
18
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-1.5">failed / total</p>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ import { computed } from 'vue';
25
+ import { formatNumber } from '../../utils/formatters';
26
+
27
+ const props = defineProps({
28
+ data: Object,
29
+ messages: Object,
30
+ });
31
+
32
+ const avgThroughput = computed(() => {
33
+ if (!props.data?.throughput?.length) return '0.00';
34
+
35
+ const throughput = props.data.throughput;
36
+ const total = throughput.reduce((sum, t) => sum + (t.processedPerSecond || t.processed || 0), 0);
37
+ const avg = total / throughput.length;
38
+
39
+ return avg.toFixed(2);
40
+ });
41
+
42
+ const totalProcessed = computed(() => {
43
+ return props.messages?.completed || 0;
44
+ });
45
+
46
+ const errorRate = computed(() => {
47
+ const failed = props.messages?.failed || 0;
48
+ const dlq = props.messages?.deadLetter || 0;
49
+ const total = props.messages?.total || 0;
50
+
51
+ if (total === 0) return '0.00';
52
+
53
+ const rate = ((failed + dlq) / total) * 100;
54
+ return rate.toFixed(2);
55
+ });
56
+ </script>
57
+
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <div class="h-80 flex items-center justify-center">
3
+ <div class="w-full max-w-sm">
4
+ <Doughnut v-if="chartData" :data="chartData" :options="chartOptions" />
5
+ <div v-else class="text-center text-gray-500 text-sm">
6
+ No message data available
7
+ </div>
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup>
13
+ import { computed } from 'vue';
14
+ import { Doughnut } from 'vue-chartjs';
15
+ import {
16
+ Chart as ChartJS,
17
+ ArcElement,
18
+ Tooltip,
19
+ Legend,
20
+ } from 'chart.js';
21
+ import { getStatusColors } from '../../utils/colors';
22
+
23
+ ChartJS.register(
24
+ ArcElement,
25
+ Tooltip,
26
+ Legend
27
+ );
28
+
29
+ const props = defineProps({
30
+ messages: Object,
31
+ });
32
+
33
+ const chartData = computed(() => {
34
+ if (!props.messages) return null;
35
+
36
+ const data = [
37
+ props.messages.pending || 0,
38
+ props.messages.processing || 0,
39
+ props.messages.completed || 0,
40
+ props.messages.failed || 0,
41
+ props.messages.deadLetter || 0,
42
+ ];
43
+
44
+ // Only show chart if there's data
45
+ if (data.every(v => v === 0)) return null;
46
+
47
+ return {
48
+ labels: ['Pending', 'Processing', 'Completed', 'Failed', 'Dead Letter'],
49
+ datasets: [
50
+ {
51
+ data: data,
52
+ backgroundColor: [
53
+ 'rgba(236, 72, 153, 0.8)', // Pink - Pending
54
+ 'rgba(168, 85, 247, 0.8)', // Purple - Processing
55
+ 'rgba(139, 92, 246, 0.8)', // Violet - Completed
56
+ 'rgba(244, 63, 94, 0.8)', // Rose - Failed
57
+ 'rgba(192, 132, 252, 0.7)', // Light Purple - Dead Letter
58
+ ],
59
+ borderColor: [
60
+ 'rgb(236, 72, 153)',
61
+ 'rgb(168, 85, 247)',
62
+ 'rgb(139, 92, 246)',
63
+ 'rgb(244, 63, 94)',
64
+ 'rgb(192, 132, 252)',
65
+ ],
66
+ borderWidth: 2,
67
+ },
68
+ ],
69
+ };
70
+ });
71
+
72
+ const chartOptions = {
73
+ responsive: true,
74
+ maintainAspectRatio: true,
75
+ plugins: {
76
+ legend: {
77
+ position: 'bottom',
78
+ labels: {
79
+ padding: 15,
80
+ font: {
81
+ size: 12,
82
+ },
83
+ color: '#6b7280',
84
+ usePointStyle: true,
85
+ pointStyle: 'circle',
86
+ },
87
+ },
88
+ tooltip: {
89
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
90
+ padding: 12,
91
+ cornerRadius: 8,
92
+ titleFont: {
93
+ size: 13,
94
+ },
95
+ bodyFont: {
96
+ size: 12,
97
+ },
98
+ callbacks: {
99
+ label: (context) => {
100
+ const label = context.label || '';
101
+ const value = context.parsed || 0;
102
+ const total = context.dataset.data.reduce((a, b) => a + b, 0);
103
+ const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
104
+ return ` ${label}: ${value.toLocaleString()} (${percentage}%)`;
105
+ },
106
+ },
107
+ },
108
+ },
109
+ };
110
+ </script>
111
+