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,496 @@
1
+ <template>
2
+ <div class="page-flat">
3
+ <div class="py-4 px-3">
4
+ <div class="space-y-2.5">
5
+ <!-- Filters with New Queue Button -->
6
+ <div class="filter-flat">
7
+ <div class="flex flex-col sm:flex-row gap-3">
8
+ <div class="flex-1">
9
+ <input
10
+ v-model="searchQuery"
11
+ type="text"
12
+ placeholder="Search queues..."
13
+ class="input"
14
+ />
15
+ </div>
16
+
17
+ <select v-model="namespaceFilter" class="input sm:w-48">
18
+ <option value="">All Namespaces</option>
19
+ <option v-for="ns in namespaces" :key="ns.namespace" :value="ns.namespace">
20
+ {{ ns.namespace }} ({{ ns.queues }})
21
+ </option>
22
+ </select>
23
+
24
+ <button
25
+ v-if="searchQuery || namespaceFilter"
26
+ @click="clearFilters"
27
+ class="btn btn-secondary whitespace-nowrap"
28
+ >
29
+ Clear Filters
30
+ </button>
31
+
32
+ <button @click="showCreateModal = true" class="btn btn-primary whitespace-nowrap">
33
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
35
+ </svg>
36
+ <span class="hidden sm:inline">New Queue</span>
37
+ </button>
38
+ </div>
39
+ </div>
40
+
41
+ <LoadingSpinner v-if="loading && !queues.length" />
42
+
43
+ <div v-else-if="error" class="error-flat">
44
+ <p><strong>Error loading queues:</strong> {{ error }}</p>
45
+ </div>
46
+
47
+ <!-- Queues Table -->
48
+ <div v-else class="table-flat">
49
+ <div class="table-container scrollbar-thin">
50
+ <table class="table">
51
+ <thead>
52
+ <tr>
53
+ <th @click="sortBy('name')" class="cursor-pointer hover:text-rose-600 dark:hover:text-rose-400 transition-colors">
54
+ <div class="flex items-center gap-1">
55
+ Queue Name
56
+ <svg class="w-3 h-3 transition-transform" :class="getSortClass('name')" fill="none" stroke="currentColor" viewBox="0 0 24 24">
57
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
58
+ </svg>
59
+ </div>
60
+ </th>
61
+ <th @click="sortBy('namespace')" class="hidden md:table-cell cursor-pointer hover:text-rose-600 dark:hover:text-rose-400 transition-colors">
62
+ <div class="flex items-center gap-1">
63
+ Namespace
64
+ <svg class="w-3 h-3 transition-transform" :class="getSortClass('namespace')" fill="none" stroke="currentColor" viewBox="0 0 24 24">
65
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
66
+ </svg>
67
+ </div>
68
+ </th>
69
+ <th @click="sortBy('partitions')" class="text-right cursor-pointer hover:text-rose-600 dark:hover:text-rose-400 transition-colors">
70
+ <div class="flex items-center justify-end gap-1">
71
+ Partitions
72
+ <svg class="w-3 h-3 transition-transform" :class="getSortClass('partitions')" fill="none" stroke="currentColor" viewBox="0 0 24 24">
73
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
74
+ </svg>
75
+ </div>
76
+ </th>
77
+ <th @click="sortBy('pending')" class="text-right cursor-pointer hover:text-rose-600 dark:hover:text-rose-400 transition-colors">
78
+ <div class="flex items-center justify-end gap-1">
79
+ Pending
80
+ <svg class="w-3 h-3 transition-transform" :class="getSortClass('pending')" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
82
+ </svg>
83
+ </div>
84
+ </th>
85
+ <th @click="sortBy('processing')" class="text-right hidden sm:table-cell cursor-pointer hover:text-rose-600 dark:hover:text-rose-400 transition-colors">
86
+ <div class="flex items-center justify-end gap-1">
87
+ Processing
88
+ <svg class="w-3 h-3 transition-transform" :class="getSortClass('processing')" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
90
+ </svg>
91
+ </div>
92
+ </th>
93
+ <th @click="sortBy('total')" class="text-right cursor-pointer hover:text-rose-600 dark:hover:text-rose-400 transition-colors">
94
+ <div class="flex items-center justify-end gap-1">
95
+ Total
96
+ <svg class="w-3 h-3 transition-transform" :class="getSortClass('total')" fill="none" stroke="currentColor" viewBox="0 0 24 24">
97
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
98
+ </svg>
99
+ </div>
100
+ </th>
101
+ <th class="text-right">Actions</th>
102
+ </tr>
103
+ </thead>
104
+ <tbody>
105
+ <tr
106
+ v-for="queue in paginatedQueues"
107
+ :key="queue.id"
108
+ class="cursor-pointer"
109
+ @click="navigateToQueue(queue.name)"
110
+ >
111
+ <td>
112
+ <div class="font-medium text-gray-900 dark:text-gray-100">{{ queue.name }}</div>
113
+ <div v-if="queue.namespace" class="text-xs text-gray-500 dark:text-gray-400 md:hidden">
114
+ {{ queue.namespace }}
115
+ </div>
116
+ </td>
117
+ <td class="hidden md:table-cell">
118
+ <span v-if="queue.namespace" class="badge badge-info">{{ queue.namespace }}</span>
119
+ <span v-else class="text-gray-400">-</span>
120
+ </td>
121
+ <td class="text-right font-medium">{{ queue.partitions }}</td>
122
+ <td class="text-right font-medium">{{ formatNumber(queue.messages?.pending || 0) }}</td>
123
+ <td class="text-right font-medium hidden sm:table-cell">{{ formatNumber(queue.messages?.processing || 0) }}</td>
124
+ <td class="text-right font-medium">{{ formatNumber(queue.messages?.total || 0) }}</td>
125
+ <td class="text-right" @click.stop>
126
+ <button
127
+ @click="confirmDelete(queue)"
128
+ class="p-1.5 rounded hover:bg-red-50 dark:hover:bg-red-900/20 text-red-600 dark:text-red-400 transition-colors"
129
+ title="Delete queue"
130
+ >
131
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
132
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
133
+ </svg>
134
+ </button>
135
+ </td>
136
+ </tr>
137
+ </tbody>
138
+ </table>
139
+
140
+ <div v-if="!filteredQueues.length" class="text-center py-12 text-gray-500 text-sm">
141
+ <svg class="w-12 h-12 mx-auto mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
142
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
143
+ </svg>
144
+ <p>No queues found</p>
145
+ <button @click="showCreateModal = true" class="btn btn-primary mt-3">
146
+ Create your first queue
147
+ </button>
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Pagination -->
152
+ <div v-if="totalPages > 1" class="flex items-center justify-between mt-4 pt-4 border-t border-gray-200/30 dark:border-gray-700/30">
153
+ <div class="text-sm text-gray-600 dark:text-gray-400">
154
+ Showing {{ startIndex + 1 }}-{{ endIndex }} of {{ filteredQueues.length }}
155
+ </div>
156
+ <div class="flex gap-2">
157
+ <button
158
+ @click="currentPage--"
159
+ :disabled="currentPage === 1"
160
+ class="btn btn-secondary px-3 py-1.5"
161
+ >
162
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
163
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
164
+ </svg>
165
+ </button>
166
+ <span class="px-3 py-1.5 text-sm">{{ currentPage }} / {{ totalPages }}</span>
167
+ <button
168
+ @click="currentPage++"
169
+ :disabled="currentPage === totalPages"
170
+ class="btn btn-secondary px-3 py-1.5"
171
+ >
172
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
173
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
174
+ </svg>
175
+ </button>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Create Queue Modal -->
181
+ <CreateQueueModal
182
+ :is-open="showCreateModal"
183
+ @close="showCreateModal = false"
184
+ @created="onQueueCreated"
185
+ />
186
+
187
+ <!-- Delete Confirmation Modal -->
188
+ <ConfirmDialog
189
+ :is-open="showDeleteConfirm"
190
+ title="Delete Queue"
191
+ :message="`Are you sure you want to delete '${queueToDelete?.name}'? This action cannot be undone.`"
192
+ confirm-text="Delete"
193
+ confirm-class="btn-danger"
194
+ @confirm="deleteQueue"
195
+ @cancel="showDeleteConfirm = false"
196
+ />
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </template>
201
+
202
+ <script setup>
203
+ import { ref, computed, onMounted, onUnmounted } from 'vue';
204
+ import { useRouter } from 'vue-router';
205
+ import { queuesApi } from '../api/queues';
206
+ import { resourcesApi } from '../api/resources';
207
+ import { formatNumber } from '../utils/formatters';
208
+
209
+ import CreateQueueModal from '../components/queues/CreateQueueModal.vue';
210
+ import ConfirmDialog from '../components/common/ConfirmDialog.vue';
211
+ import LoadingSpinner from '../components/common/LoadingSpinner.vue';
212
+
213
+ const router = useRouter();
214
+
215
+ const loading = ref(false);
216
+ const error = ref(null);
217
+ const queues = ref([]);
218
+ const namespaces = ref([]);
219
+ const searchQuery = ref('');
220
+ const namespaceFilter = ref('');
221
+ const sortColumn = ref('name');
222
+ const sortDirection = ref('asc');
223
+ const currentPage = ref(1);
224
+ const itemsPerPage = 20;
225
+
226
+ const showCreateModal = ref(false);
227
+ const showDeleteConfirm = ref(false);
228
+ const queueToDelete = ref(null);
229
+
230
+ const filteredQueues = computed(() => {
231
+ let filtered = queues.value;
232
+
233
+ if (searchQuery.value) {
234
+ const search = searchQuery.value.toLowerCase();
235
+ filtered = filtered.filter(q =>
236
+ q.name.toLowerCase().includes(search) ||
237
+ q.namespace?.toLowerCase().includes(search)
238
+ );
239
+ }
240
+
241
+ if (namespaceFilter.value) {
242
+ filtered = filtered.filter(q => q.namespace === namespaceFilter.value);
243
+ }
244
+
245
+ filtered = [...filtered].sort((a, b) => {
246
+ let aVal, bVal;
247
+
248
+ switch (sortColumn.value) {
249
+ case 'name':
250
+ aVal = a.name.toLowerCase();
251
+ bVal = b.name.toLowerCase();
252
+ break;
253
+ case 'namespace':
254
+ aVal = a.namespace?.toLowerCase() || '';
255
+ bVal = b.namespace?.toLowerCase() || '';
256
+ break;
257
+ case 'partitions':
258
+ aVal = a.partitions || 0;
259
+ bVal = b.partitions || 0;
260
+ break;
261
+ case 'pending':
262
+ aVal = a.messages?.pending || 0;
263
+ bVal = b.messages?.pending || 0;
264
+ break;
265
+ case 'processing':
266
+ aVal = a.messages?.processing || 0;
267
+ bVal = b.messages?.processing || 0;
268
+ break;
269
+ case 'total':
270
+ aVal = a.messages?.total || 0;
271
+ bVal = b.messages?.total || 0;
272
+ break;
273
+ default:
274
+ return 0;
275
+ }
276
+
277
+ if (aVal < bVal) return sortDirection.value === 'asc' ? -1 : 1;
278
+ if (aVal > bVal) return sortDirection.value === 'asc' ? 1 : -1;
279
+ return 0;
280
+ });
281
+
282
+ return filtered;
283
+ });
284
+
285
+ const totalPages = computed(() => Math.ceil(filteredQueues.value.length / itemsPerPage));
286
+ const startIndex = computed(() => (currentPage.value - 1) * itemsPerPage);
287
+ const endIndex = computed(() => Math.min(startIndex.value + itemsPerPage, filteredQueues.value.length));
288
+ const paginatedQueues = computed(() => filteredQueues.value.slice(startIndex.value, endIndex.value));
289
+
290
+ function sortBy(column) {
291
+ if (sortColumn.value === column) {
292
+ sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc';
293
+ } else {
294
+ sortColumn.value = column;
295
+ sortDirection.value = 'asc';
296
+ }
297
+ }
298
+
299
+ function getSortClass(column) {
300
+ if (sortColumn.value !== column) {
301
+ return 'opacity-30';
302
+ }
303
+ return sortDirection.value === 'asc' ? '' : 'rotate-180';
304
+ }
305
+
306
+ function navigateToQueue(queueName) {
307
+ router.push(`/queues/${queueName}`);
308
+ }
309
+
310
+ function confirmDelete(queue) {
311
+ queueToDelete.value = queue;
312
+ showDeleteConfirm.value = true;
313
+ }
314
+
315
+ async function deleteQueue() {
316
+ if (!queueToDelete.value) return;
317
+
318
+ try {
319
+ await queuesApi.deleteQueue(queueToDelete.value.name);
320
+ showDeleteConfirm.value = false;
321
+ queueToDelete.value = null;
322
+ await loadData();
323
+ } catch (err) {
324
+ error.value = err.message;
325
+ }
326
+ }
327
+
328
+ async function loadData() {
329
+ loading.value = true;
330
+ error.value = null;
331
+
332
+ try {
333
+ const [queuesRes, namespacesRes] = await Promise.all([
334
+ queuesApi.getQueues(),
335
+ resourcesApi.getNamespaces(),
336
+ ]);
337
+
338
+ queues.value = queuesRes.data.queues;
339
+ namespaces.value = namespacesRes.data.namespaces;
340
+ } catch (err) {
341
+ error.value = err.message;
342
+ console.error('Queues error:', err);
343
+ } finally {
344
+ loading.value = false;
345
+ }
346
+ }
347
+
348
+ async function onQueueCreated() {
349
+ await loadData();
350
+ }
351
+
352
+ function clearFilters() {
353
+ searchQuery.value = '';
354
+ namespaceFilter.value = '';
355
+ }
356
+
357
+ onMounted(() => {
358
+ loadData();
359
+
360
+ if (window.registerRefreshCallback) {
361
+ window.registerRefreshCallback('/queues', loadData);
362
+ }
363
+ });
364
+
365
+ onUnmounted(() => {
366
+ if (window.registerRefreshCallback) {
367
+ window.registerRefreshCallback('/queues', null);
368
+ }
369
+ });
370
+ </script>
371
+
372
+ <style scoped>
373
+ .page-flat {
374
+ min-height: 100%;
375
+ }
376
+
377
+ .filter-flat {
378
+ background: #ffffff;
379
+ border: none;
380
+ box-shadow: none;
381
+ border-radius: 0.75rem;
382
+ padding: 1rem;
383
+ }
384
+
385
+ .dark .filter-flat {
386
+ background: #0a0d14;
387
+ }
388
+
389
+ /* Override input styles for flat design */
390
+ .filter-flat :deep(.input) {
391
+ background: transparent;
392
+ border: 1px solid rgba(156, 163, 175, 0.15);
393
+ transition: all 0.2s ease;
394
+ }
395
+
396
+ .filter-flat :deep(.input:hover) {
397
+ border-color: rgba(156, 163, 175, 0.25);
398
+ }
399
+
400
+ .filter-flat :deep(.input:focus) {
401
+ background: rgba(244, 63, 94, 0.02);
402
+ border-color: rgba(244, 63, 94, 0.4);
403
+ box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.05);
404
+ }
405
+
406
+ .dark .filter-flat :deep(.input) {
407
+ border-color: rgba(156, 163, 175, 0.1);
408
+ }
409
+
410
+ .dark .filter-flat :deep(.input:hover) {
411
+ border-color: rgba(156, 163, 175, 0.2);
412
+ }
413
+
414
+ .dark .filter-flat :deep(.input:focus) {
415
+ background: rgba(244, 63, 94, 0.03);
416
+ border-color: rgba(244, 63, 94, 0.5);
417
+ box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.08);
418
+ }
419
+
420
+ .table-flat {
421
+ background: #ffffff;
422
+ border: none;
423
+ box-shadow: none;
424
+ border-radius: 0.75rem;
425
+ padding: 0;
426
+ }
427
+
428
+ .dark .table-flat {
429
+ background: #0a0d14;
430
+ }
431
+
432
+ /* Flat table styling - zebra stripes, no borders */
433
+ .table-flat :deep(.table) {
434
+ border-collapse: separate;
435
+ border-spacing: 0;
436
+ }
437
+
438
+ .table-flat :deep(.table thead) {
439
+ background: transparent;
440
+ border-bottom: 1px solid rgba(156, 163, 175, 0.08);
441
+ }
442
+
443
+ .dark .table-flat :deep(.table thead) {
444
+ border-bottom-color: rgba(156, 163, 175, 0.1);
445
+ }
446
+
447
+ .table-flat :deep(.table th) {
448
+ padding: 1rem 1rem;
449
+ font-weight: 600;
450
+ letter-spacing: 0.05em;
451
+ }
452
+
453
+ .table-flat :deep(.table tbody tr) {
454
+ border: none;
455
+ transition: all 0.15s ease;
456
+ }
457
+
458
+ /* Zebra striping */
459
+ .table-flat :deep(.table tbody tr:nth-child(even)) {
460
+ background: rgba(0, 0, 0, 0.015);
461
+ }
462
+
463
+ .dark .table-flat :deep(.table tbody tr:nth-child(even)) {
464
+ background: rgba(255, 255, 255, 0.02);
465
+ }
466
+
467
+ /* Hover effect with left accent */
468
+ .table-flat :deep(.table tbody tr:hover) {
469
+ background: rgba(244, 63, 94, 0.03);
470
+ box-shadow: inset 3px 0 0 0 rgba(244, 63, 94, 0.6);
471
+ }
472
+
473
+ .dark .table-flat :deep(.table tbody tr:hover) {
474
+ background: rgba(244, 63, 94, 0.05);
475
+ box-shadow: inset 3px 0 0 0 rgba(244, 63, 94, 0.8);
476
+ }
477
+
478
+ .table-flat :deep(.table td) {
479
+ padding: 0.875rem 1rem;
480
+ border: none;
481
+ }
482
+
483
+ .error-flat {
484
+ background: transparent;
485
+ border: none;
486
+ box-shadow: none;
487
+ border-radius: 0.75rem;
488
+ padding: 1rem;
489
+ color: #dc2626;
490
+ font-size: 0.875rem;
491
+ }
492
+
493
+ .dark .error-flat {
494
+ color: #fca5a5;
495
+ }
496
+ </style>
@@ -0,0 +1,25 @@
1
+ export default {
2
+ content: [
3
+ "./index.html",
4
+ "./src/**/*.{vue,js}",
5
+ ],
6
+ darkMode: 'class',
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ // Primary brand colors - Rose & Purple theme
11
+ 'queen-rose': '#f43f5e', // rose-500
12
+ 'queen-purple': '#a855f7', // purple-500
13
+ 'queen-pink': '#ec4899', // pink-500
14
+ // Aliases for primary/secondary
15
+ 'primary': '#f43f5e',
16
+ 'secondary': '#a855f7',
17
+ },
18
+ fontFamily: {
19
+ sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'sans-serif'],
20
+ mono: ['JetBrains Mono', 'Courier New', 'monospace'],
21
+ },
22
+ },
23
+ },
24
+ plugins: [],
25
+ };
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ server: {
7
+ port: 4000,
8
+ },
9
+ });
10
+
@@ -1,3 +0,0 @@
1
- {
2
- "recommendations": ["Vue.volar"]
3
- }
@@ -1,5 +0,0 @@
1
- # Vue 3 + Vite
2
-
3
- This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
4
-
5
- Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
@@ -1,14 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
7
- <title>Queen Dashboard - Message Queue Management</title>
8
- <meta name="description" content="Real-time monitoring and management dashboard for Queen Message Queue System">
9
- </head>
10
- <body>
11
- <div id="app"></div>
12
- <script type="module" src="/src/main.js"></script>
13
- </body>
14
- </html>