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
|
@@ -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
|
+
|