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.
Files changed (160) hide show
  1. package/API.md +862 -752
  2. package/AUTH.md +2044 -0
  3. package/LICENSE.md +202 -0
  4. package/README.md +1705 -1051
  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-complete-client.js +260 -0
  17. package/examples/test-dashboard-api.js +200 -0
  18. package/examples/test-traceid.js +147 -0
  19. package/package.json +17 -4
  20. package/server.log +1 -0
  21. package/src/benchmark/consumer.js +207 -0
  22. package/src/benchmark/consumer_multi.js +216 -0
  23. package/src/benchmark/producer.js +75 -0
  24. package/src/benchmark/producer_multi.js +115 -0
  25. package/src/client/client.js +300 -31
  26. package/src/client/queenClient.js +5 -0
  27. package/src/cluster-server.js +242 -0
  28. package/src/config.js +19 -5
  29. package/src/database/connection.js +42 -16
  30. package/src/database/poolManager.js +7 -0
  31. package/src/database/schema-v2.sql +194 -130
  32. package/src/managers/queueManagerOptimized.js +823 -933
  33. package/src/managers/systemEventManager.js +8 -3
  34. package/src/routes/messages.js +127 -57
  35. package/src/routes/pop.js +27 -43
  36. package/src/routes/resources.js +61 -27
  37. package/src/routes/status.js +1037 -0
  38. package/src/server.js +308 -272
  39. package/src/services/evictionService.js +57 -28
  40. package/src/services/retentionService.js +44 -11
  41. package/src/test/MIGRATION_ISSUES.md +174 -0
  42. package/src/test/README.md +203 -0
  43. package/src/test/advanced-pattern-tests.js +1137 -0
  44. package/src/test/bus-mode-tests.js +361 -0
  45. package/src/test/core-tests.js +342 -0
  46. package/src/test/edge-case-tests.js +561 -0
  47. package/src/test/enterprise-tests.js +637 -0
  48. package/src/test/partition-locking-tests.js +545 -0
  49. package/src/test/test-new.js +278 -0
  50. package/src/test/test.js +6 -3
  51. package/src/test/utils.js +169 -0
  52. package/src/utils/streaming.js +231 -0
  53. package/src/utils/uuid.js +2 -2
  54. package/src/websocket/wsServer.js +10 -3
  55. package/test-keepalive-v2.sh +22 -0
  56. package/webapp/COLOR_GUIDE.md +118 -0
  57. package/webapp/README.md +143 -0
  58. package/webapp/index.html +14 -0
  59. package/webapp/package-lock.json +3184 -0
  60. package/webapp/package.json +25 -0
  61. package/webapp/postcss.config.js +7 -0
  62. package/webapp/public/assets/queen-logo-blue.svg +210 -0
  63. package/webapp/public/assets/queen-logo-cyan.svg +210 -0
  64. package/webapp/public/assets/queen-logo-indigo.svg +210 -0
  65. package/webapp/public/assets/queen-logo-orange.svg +210 -0
  66. package/webapp/public/assets/queen-logo-pink.svg +210 -0
  67. package/webapp/public/assets/queen-logo-purple.svg +210 -0
  68. package/webapp/public/assets/queen-logo-rose.svg +239 -0
  69. package/webapp/public/assets/queen-logo.svg +263 -0
  70. package/webapp/src/App.vue +19 -0
  71. package/webapp/src/api/analytics.js +10 -0
  72. package/webapp/src/api/client.js +29 -0
  73. package/webapp/src/api/consumers.js +52 -0
  74. package/webapp/src/api/health.js +7 -0
  75. package/webapp/src/api/messages.js +26 -0
  76. package/webapp/src/api/queues.js +14 -0
  77. package/webapp/src/api/resources.js +8 -0
  78. package/webapp/src/assets/styles/main.css +357 -0
  79. package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
  80. package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
  81. package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
  82. package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
  83. package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
  84. package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
  85. package/webapp/src/components/common/ConfirmDialog.vue +56 -0
  86. package/webapp/src/components/common/LoadingSpinner.vue +6 -0
  87. package/webapp/src/components/common/MetricCard.vue +43 -0
  88. package/webapp/src/components/common/StatusBadge.vue +45 -0
  89. package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
  90. package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
  91. package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
  92. package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
  93. package/webapp/src/components/layout/AppLayout.vue +110 -0
  94. package/webapp/src/components/layout/AppSidebar.vue +304 -0
  95. package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
  96. package/webapp/src/components/messages/MessageFilters.vue +114 -0
  97. package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
  98. package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
  99. package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
  100. package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
  101. package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
  102. package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
  103. package/webapp/src/components/queues/QueueFilters.vue +90 -0
  104. package/webapp/src/composables/useApi.js +34 -0
  105. package/webapp/src/composables/useTheme.js +36 -0
  106. package/webapp/src/main.js +11 -0
  107. package/webapp/src/router/index.js +42 -0
  108. package/webapp/src/utils/colors.js +96 -0
  109. package/webapp/src/utils/formatters.js +49 -0
  110. package/webapp/src/views/Analytics.vue +377 -0
  111. package/webapp/src/views/ConsumerGroups.vue +433 -0
  112. package/webapp/src/views/Dashboard.vue +418 -0
  113. package/webapp/src/views/Messages.vue +361 -0
  114. package/webapp/src/views/QueueDetail.vue +582 -0
  115. package/webapp/src/views/Queues.vue +496 -0
  116. package/webapp/tailwind.config.js +25 -0
  117. package/webapp/vite.config.js +10 -0
  118. package/CACHE.md +0 -519
  119. package/DASHBOARD-V3.md +0 -478
  120. package/DASHBOARD.md +0 -382
  121. package/MOD_QUEUE.md +0 -453
  122. package/PARTITION_LOCKING_DESIGN.md +0 -989
  123. package/PLAN.md +0 -707
  124. package/QUERY_ANALSYS.md +0 -72
  125. package/QUEUE_BUS.md +0 -334
  126. package/V2-PLAN.md +0 -236
  127. package/dashboard/.vscode/extensions.json +0 -3
  128. package/dashboard/README.md +0 -5
  129. package/dashboard/index.html +0 -14
  130. package/dashboard/package-lock.json +0 -1458
  131. package/dashboard/package.json +0 -25
  132. package/dashboard/public/vite.svg +0 -1
  133. package/dashboard/src/App.vue +0 -29
  134. package/dashboard/src/assets/styles/main.css +0 -908
  135. package/dashboard/src/assets/vue.svg +0 -1
  136. package/dashboard/src/components/cards/MetricCard.vue +0 -298
  137. package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
  138. package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
  139. package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
  140. package/dashboard/src/components/common/ActivityFeed.vue +0 -251
  141. package/dashboard/src/components/layout/AppHeader.vue +0 -208
  142. package/dashboard/src/components/layout/AppLayout.vue +0 -88
  143. package/dashboard/src/components/layout/AppSidebar.vue +0 -261
  144. package/dashboard/src/main.js +0 -44
  145. package/dashboard/src/router.js +0 -54
  146. package/dashboard/src/services/api.js +0 -187
  147. package/dashboard/src/services/websocket.js +0 -167
  148. package/dashboard/src/utils/constants.js +0 -56
  149. package/dashboard/src/utils/helpers.js +0 -118
  150. package/dashboard/src/views/Analytics.vue +0 -912
  151. package/dashboard/src/views/Dashboard.vue +0 -906
  152. package/dashboard/src/views/Messages.vue +0 -437
  153. package/dashboard/src/views/QueueDetail.vue +0 -501
  154. package/dashboard/src/views/Queues.vue +0 -333
  155. package/dashboard/vite.config.js +0 -30
  156. package/debug-namespace.js +0 -110
  157. package/docs/long-polling.md +0 -159
  158. package/docs/multi-server-cache-solutions.md +0 -185
  159. package/docs/performance-tuning.md +0 -222
  160. 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
+