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,53 @@
1
+ <template>
2
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
3
+ <div>
4
+ <div class="flex items-center gap-3 mb-2">
5
+ <button @click="goBack" class="p-1 hover:bg-gray-100 dark:hover:bg-slate-700 rounded transition-colors">
6
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
7
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
8
+ </svg>
9
+ </button>
10
+ <h2 class="text-xl font-bold">{{ queue?.name }}</h2>
11
+ </div>
12
+ <div class="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400 ml-9">
13
+ <span>Namespace: {{ queue?.namespace || '-' }}</span>
14
+ <span>Task: {{ queue?.task || '-' }}</span>
15
+ <span>Priority: {{ queue?.priority || 0 }}</span>
16
+ <span class="text-xs">Created: {{ formatDate(queue?.createdAt) }}</span>
17
+ </div>
18
+ </div>
19
+
20
+ <div class="flex gap-2 ml-9 sm:ml-0">
21
+ <button @click="$emit('push-message')" class="btn btn-primary">
22
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
23
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
24
+ </svg>
25
+ Push Message
26
+ </button>
27
+ <button @click="$emit('clear-queue')" class="btn btn-secondary">
28
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
29
+ <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" />
30
+ </svg>
31
+ Clear Queue
32
+ </button>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { useRouter } from 'vue-router';
39
+ import { formatDate } from '../../utils/formatters';
40
+
41
+ defineProps({
42
+ queue: Object,
43
+ });
44
+
45
+ defineEmits(['push-message', 'clear-queue']);
46
+
47
+ const router = useRouter();
48
+
49
+ function goBack() {
50
+ router.push('/queues');
51
+ }
52
+ </script>
53
+
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="messages-container">
3
+ <h3 class="text-base font-semibold mb-4">Recent Messages</h3>
4
+
5
+ <div class="table-container scrollbar-thin">
6
+ <table class="table">
7
+ <thead>
8
+ <tr>
9
+ <th>Transaction ID</th>
10
+ <th class="hidden sm:table-cell">Partition</th>
11
+ <th class="text-right">Created</th>
12
+ <th class="text-right hidden md:table-cell">Status</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <tr
17
+ v-for="message in messages"
18
+ :key="message.id"
19
+ class="cursor-pointer"
20
+ @click="$emit('message-click', message)"
21
+ >
22
+ <td>
23
+ <div class="font-mono text-xs">{{ message.transactionId?.substring(0, 16) }}...</div>
24
+ </td>
25
+ <td class="hidden sm:table-cell">
26
+ <span class="text-xs">{{ message.partition }}</span>
27
+ </td>
28
+ <td class="text-right text-xs">{{ formatTime(message.createdAt) }}</td>
29
+ <td class="text-right hidden md:table-cell">
30
+ <StatusBadge v-if="message.status" :status="message.status" />
31
+ </td>
32
+ </tr>
33
+ </tbody>
34
+ </table>
35
+
36
+ <div v-if="!messages?.length" class="text-center py-8 text-gray-500 text-sm">
37
+ No recent messages
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </template>
42
+
43
+ <style scoped>
44
+ .messages-container {
45
+ background: #ffffff;
46
+ border: none;
47
+ box-shadow: none;
48
+ border-radius: 0.75rem;
49
+ padding: 1rem;
50
+ transition: all 0.3s ease;
51
+ }
52
+
53
+ .dark .messages-container {
54
+ background: #0a0d14;
55
+ }
56
+
57
+ .messages-container:hover {
58
+ background: #fafafa;
59
+ }
60
+
61
+ .dark .messages-container:hover {
62
+ background: #0d1117;
63
+ }
64
+ </style>
65
+
66
+ <script setup>
67
+ import { formatTime } from '../../utils/formatters';
68
+ import StatusBadge from '../common/StatusBadge.vue';
69
+
70
+ defineProps({
71
+ messages: Array,
72
+ });
73
+
74
+ defineEmits(['message-click']);
75
+ </script>
76
+
@@ -0,0 +1,193 @@
1
+ <template>
2
+ <div v-if="isOpen" class="fixed inset-0 z-50 overflow-y-auto">
3
+ <div class="flex items-center justify-center min-h-screen px-4">
4
+ <!-- Backdrop with blur -->
5
+ <div
6
+ class="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm transition-opacity animate-fade-in"
7
+ @click="close"
8
+ ></div>
9
+
10
+ <!-- Modal -->
11
+ <div class="relative bg-white dark:bg-slate-800 rounded-xl shadow-2xl max-w-md w-full p-6 animate-scale-in border border-gray-200 dark:border-gray-700">
12
+ <div class="flex items-center justify-between mb-4">
13
+ <h3 class="text-lg font-bold">Create New Queue</h3>
14
+ <button @click="close" class="text-gray-400 hover:text-gray-600">
15
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
16
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
17
+ </svg>
18
+ </button>
19
+ </div>
20
+
21
+ <form @submit.prevent="createQueue" class="space-y-4">
22
+ <div>
23
+ <label class="block text-sm font-medium mb-1">Queue Name *</label>
24
+ <input
25
+ v-model="form.queue"
26
+ type="text"
27
+ required
28
+ placeholder="my-queue"
29
+ class="input"
30
+ />
31
+ </div>
32
+
33
+ <div>
34
+ <label class="block text-sm font-medium mb-1">Partition</label>
35
+ <input
36
+ v-model="form.partition"
37
+ type="text"
38
+ placeholder="Default"
39
+ class="input"
40
+ />
41
+ </div>
42
+
43
+ <div class="grid grid-cols-2 gap-3">
44
+ <div>
45
+ <label class="block text-sm font-medium mb-1">TTL (seconds)</label>
46
+ <input
47
+ v-model.number="form.ttl"
48
+ type="number"
49
+ placeholder="3600"
50
+ class="input"
51
+ />
52
+ </div>
53
+
54
+ <div>
55
+ <label class="block text-sm font-medium mb-1">Priority</label>
56
+ <input
57
+ v-model.number="form.priority"
58
+ type="number"
59
+ min="0"
60
+ max="100"
61
+ placeholder="0"
62
+ class="input"
63
+ />
64
+ </div>
65
+ </div>
66
+
67
+ <div class="grid grid-cols-2 gap-3">
68
+ <div>
69
+ <label class="block text-sm font-medium mb-1">Namespace</label>
70
+ <input
71
+ v-model="form.namespace"
72
+ type="text"
73
+ placeholder="optional"
74
+ class="input"
75
+ />
76
+ </div>
77
+
78
+ <div>
79
+ <label class="block text-sm font-medium mb-1">Task</label>
80
+ <input
81
+ v-model="form.task"
82
+ type="text"
83
+ placeholder="optional"
84
+ class="input"
85
+ />
86
+ </div>
87
+ </div>
88
+
89
+ <div>
90
+ <label class="block text-sm font-medium mb-1">Max Queue Size</label>
91
+ <input
92
+ v-model.number="form.maxQueueSize"
93
+ type="number"
94
+ placeholder="10000"
95
+ class="input"
96
+ />
97
+ </div>
98
+
99
+ <div v-if="error" class="text-sm text-red-600 dark:text-red-400">
100
+ {{ error }}
101
+ </div>
102
+
103
+ <div class="flex gap-3 pt-2">
104
+ <button type="submit" :disabled="loading" class="btn btn-primary flex-1">
105
+ <span v-if="loading">Creating...</span>
106
+ <span v-else>Create Queue</span>
107
+ </button>
108
+ <button type="button" @click="close" class="btn btn-secondary">
109
+ Cancel
110
+ </button>
111
+ </div>
112
+ </form>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </template>
117
+
118
+ <script setup>
119
+ import { ref, watch } from 'vue';
120
+ import { queuesApi } from '../../api/queues';
121
+
122
+ const props = defineProps({
123
+ isOpen: Boolean,
124
+ });
125
+
126
+ const emit = defineEmits(['close', 'created']);
127
+
128
+ const loading = ref(false);
129
+ const error = ref(null);
130
+ const form = ref({
131
+ queue: '',
132
+ partition: 'Default',
133
+ ttl: 3600,
134
+ priority: 0,
135
+ namespace: '',
136
+ task: '',
137
+ maxQueueSize: 10000,
138
+ });
139
+
140
+ watch(() => props.isOpen, (newVal) => {
141
+ if (newVal) {
142
+ resetForm();
143
+ }
144
+ });
145
+
146
+ function resetForm() {
147
+ form.value = {
148
+ queue: '',
149
+ partition: 'Default',
150
+ ttl: 3600,
151
+ priority: 0,
152
+ namespace: '',
153
+ task: '',
154
+ maxQueueSize: 10000,
155
+ };
156
+ error.value = null;
157
+ }
158
+
159
+ async function createQueue() {
160
+ loading.value = true;
161
+ error.value = null;
162
+
163
+ try {
164
+ const payload = {
165
+ queue: form.value.queue,
166
+ partition: form.value.partition || 'Default',
167
+ ttl: form.value.ttl,
168
+ priority: form.value.priority,
169
+ maxQueueSize: form.value.maxQueueSize,
170
+ };
171
+
172
+ if (form.value.namespace) {
173
+ payload.namespace = form.value.namespace;
174
+ }
175
+ if (form.value.task) {
176
+ payload.task = form.value.task;
177
+ }
178
+
179
+ await queuesApi.configureQueue(payload);
180
+ emit('created');
181
+ close();
182
+ } catch (err) {
183
+ error.value = err.response?.data?.error || err.message;
184
+ } finally {
185
+ loading.value = false;
186
+ }
187
+ }
188
+
189
+ function close() {
190
+ emit('close');
191
+ }
192
+ </script>
193
+
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <div class="filter-container">
3
+ <div class="flex flex-col sm:flex-row gap-3">
4
+ <!-- Search -->
5
+ <div class="flex-1">
6
+ <input
7
+ v-model="localSearch"
8
+ @input="onSearchChange"
9
+ type="text"
10
+ placeholder="Search queues..."
11
+ class="input"
12
+ />
13
+ </div>
14
+
15
+ <!-- Namespace Filter -->
16
+ <select
17
+ v-model="localNamespace"
18
+ @change="onNamespaceChange"
19
+ class="input sm:w-48"
20
+ >
21
+ <option value="">All Namespaces</option>
22
+ <option v-for="ns in namespaces" :key="ns.namespace" :value="ns.namespace">
23
+ {{ ns.namespace }} ({{ ns.queues }})
24
+ </option>
25
+ </select>
26
+
27
+ <!-- Clear Filters -->
28
+ <button
29
+ v-if="localSearch || localNamespace"
30
+ @click="clearFilters"
31
+ class="btn btn-secondary whitespace-nowrap"
32
+ >
33
+ Clear Filters
34
+ </button>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <style scoped>
40
+ .filter-container {
41
+ background: #ffffff;
42
+ border: none;
43
+ box-shadow: none;
44
+ border-radius: 0.75rem;
45
+ padding: 1rem;
46
+ }
47
+
48
+ .dark .filter-container {
49
+ background: #0a0d14;
50
+ }
51
+ </style>
52
+
53
+ <script setup>
54
+ import { ref, watch } from 'vue';
55
+
56
+ const props = defineProps({
57
+ search: String,
58
+ namespace: String,
59
+ namespaces: Array,
60
+ });
61
+
62
+ const emit = defineEmits(['update:search', 'update:namespace']);
63
+
64
+ const localSearch = ref(props.search || '');
65
+ const localNamespace = ref(props.namespace || '');
66
+
67
+ watch(() => props.search, (newVal) => {
68
+ localSearch.value = newVal || '';
69
+ });
70
+
71
+ watch(() => props.namespace, (newVal) => {
72
+ localNamespace.value = newVal || '';
73
+ });
74
+
75
+ function onSearchChange() {
76
+ emit('update:search', localSearch.value);
77
+ }
78
+
79
+ function onNamespaceChange() {
80
+ emit('update:namespace', localNamespace.value);
81
+ }
82
+
83
+ function clearFilters() {
84
+ localSearch.value = '';
85
+ localNamespace.value = '';
86
+ emit('update:search', '');
87
+ emit('update:namespace', '');
88
+ }
89
+ </script>
90
+
@@ -0,0 +1,34 @@
1
+ import { ref } from 'vue';
2
+
3
+ export function useApi(apiCall) {
4
+ const data = ref(null);
5
+ const error = ref(null);
6
+ const loading = ref(false);
7
+
8
+ const execute = async (...params) => {
9
+ loading.value = true;
10
+ error.value = null;
11
+
12
+ try {
13
+ const response = await apiCall(...params);
14
+ data.value = response.data;
15
+ return response.data;
16
+ } catch (err) {
17
+ error.value = err.response?.data?.error || err.message || 'An error occurred';
18
+ throw err;
19
+ } finally {
20
+ loading.value = false;
21
+ }
22
+ };
23
+
24
+ const refresh = () => execute();
25
+
26
+ return {
27
+ data,
28
+ error,
29
+ loading,
30
+ execute,
31
+ refresh,
32
+ };
33
+ }
34
+
@@ -0,0 +1,36 @@
1
+ import { ref, watch, onMounted } from 'vue';
2
+
3
+ export function useTheme() {
4
+ const isDark = ref(false);
5
+
6
+ const toggleTheme = () => {
7
+ isDark.value = !isDark.value;
8
+ };
9
+
10
+ // Watch for changes and update localStorage and document class
11
+ watch(isDark, (newValue) => {
12
+ if (newValue) {
13
+ document.documentElement.classList.add('dark');
14
+ localStorage.setItem('theme', 'dark');
15
+ } else {
16
+ document.documentElement.classList.remove('dark');
17
+ localStorage.setItem('theme', 'light');
18
+ }
19
+ });
20
+
21
+ // Initialize theme from localStorage or system preference
22
+ onMounted(() => {
23
+ const savedTheme = localStorage.getItem('theme');
24
+ if (savedTheme) {
25
+ isDark.value = savedTheme === 'dark';
26
+ } else {
27
+ isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
28
+ }
29
+ });
30
+
31
+ return {
32
+ isDark,
33
+ toggleTheme,
34
+ };
35
+ }
36
+
@@ -0,0 +1,11 @@
1
+ import { createApp } from 'vue';
2
+ import App from './App.vue';
3
+ import router from './router';
4
+ import './assets/styles/main.css';
5
+
6
+ const app = createApp(App);
7
+
8
+ app.use(router);
9
+
10
+ app.mount('#app');
11
+
@@ -0,0 +1,42 @@
1
+ import { createRouter, createWebHistory } from 'vue-router';
2
+
3
+ const routes = [
4
+ {
5
+ path: '/',
6
+ name: 'Dashboard',
7
+ component: () => import('../views/Dashboard.vue'),
8
+ },
9
+ {
10
+ path: '/queues',
11
+ name: 'Queues',
12
+ component: () => import('../views/Queues.vue'),
13
+ },
14
+ {
15
+ path: '/queues/:queueName',
16
+ name: 'QueueDetail',
17
+ component: () => import('../views/QueueDetail.vue'),
18
+ },
19
+ {
20
+ path: '/consumer-groups',
21
+ name: 'ConsumerGroups',
22
+ component: () => import('../views/ConsumerGroups.vue'),
23
+ },
24
+ {
25
+ path: '/messages',
26
+ name: 'Messages',
27
+ component: () => import('../views/Messages.vue'),
28
+ },
29
+ {
30
+ path: '/analytics',
31
+ name: 'Analytics',
32
+ component: () => import('../views/Analytics.vue'),
33
+ },
34
+ ];
35
+
36
+ const router = createRouter({
37
+ history: createWebHistory(),
38
+ routes,
39
+ });
40
+
41
+ export default router;
42
+
@@ -0,0 +1,96 @@
1
+ // Centralized color configuration for the entire app
2
+ // Change these values to update the color scheme everywhere
3
+
4
+ export const colors = {
5
+ // Primary brand colors (used for charts, accents, etc.)
6
+ primary: {
7
+ name: 'Rose',
8
+ hex: '#f43f5e', // rose-500
9
+ rgb: 'rgb(244, 63, 94)',
10
+ rgba: (opacity) => `rgba(244, 63, 94, ${opacity})`,
11
+ },
12
+ secondary: {
13
+ name: 'Purple',
14
+ hex: '#a855f7', // purple-500
15
+ rgb: 'rgb(168, 85, 247)',
16
+ rgba: (opacity) => `rgba(168, 85, 247, ${opacity})`,
17
+ },
18
+
19
+ // Status colors
20
+ success: {
21
+ hex: '#10b981', // green-500
22
+ rgb: 'rgb(16, 185, 129)',
23
+ rgba: (opacity) => `rgba(16, 185, 129, ${opacity})`,
24
+ },
25
+ warning: {
26
+ hex: '#f59e0b', // yellow-500
27
+ rgb: 'rgb(245, 158, 11)',
28
+ rgba: (opacity) => `rgba(245, 158, 11, ${opacity})`,
29
+ },
30
+ danger: {
31
+ hex: '#ef4444', // red-500
32
+ rgb: 'rgb(239, 68, 68)',
33
+ rgba: (opacity) => `rgba(239, 68, 68, ${opacity})`,
34
+ },
35
+ info: {
36
+ hex: '#3b82f6', // blue-500
37
+ rgb: 'rgb(59, 130, 246)',
38
+ rgba: (opacity) => `rgba(59, 130, 246, ${opacity})`,
39
+ },
40
+
41
+ // Chart colors (for consistency)
42
+ charts: {
43
+ // Main data series
44
+ ingested: {
45
+ border: 'rgb(244, 63, 94)', // Rose
46
+ background: 'rgba(244, 63, 94, 0.1)',
47
+ },
48
+ processed: {
49
+ border: 'rgb(168, 85, 247)', // Purple
50
+ background: 'rgba(168, 85, 247, 0.1)',
51
+ },
52
+
53
+ // Status distribution
54
+ pending: {
55
+ bg: 'rgba(245, 158, 11, 0.8)', // Yellow
56
+ border: 'rgb(245, 158, 11)',
57
+ },
58
+ processing: {
59
+ bg: 'rgba(59, 130, 246, 0.8)', // Blue
60
+ border: 'rgb(59, 130, 246)',
61
+ },
62
+ completed: {
63
+ bg: 'rgba(16, 185, 129, 0.8)', // Green
64
+ border: 'rgb(16, 185, 129)',
65
+ },
66
+ failed: {
67
+ bg: 'rgba(239, 68, 68, 0.8)', // Red
68
+ border: 'rgb(239, 68, 68)',
69
+ },
70
+ deadLetter: {
71
+ bg: 'rgba(156, 163, 175, 0.8)', // Gray
72
+ border: 'rgb(156, 163, 175)',
73
+ },
74
+ },
75
+ };
76
+
77
+ // Helper to get all chart colors for status distribution
78
+ export function getStatusColors() {
79
+ return {
80
+ backgrounds: [
81
+ colors.charts.pending.bg,
82
+ colors.charts.processing.bg,
83
+ colors.charts.completed.bg,
84
+ colors.charts.failed.bg,
85
+ colors.charts.deadLetter.bg,
86
+ ],
87
+ borders: [
88
+ colors.charts.pending.border,
89
+ colors.charts.processing.border,
90
+ colors.charts.completed.border,
91
+ colors.charts.failed.border,
92
+ colors.charts.deadLetter.border,
93
+ ],
94
+ };
95
+ }
96
+
@@ -0,0 +1,49 @@
1
+ export function formatNumber(num) {
2
+ if (num >= 1000000) {
3
+ return (num / 1000000).toFixed(1) + 'M';
4
+ }
5
+ if (num >= 1000) {
6
+ return (num / 1000).toFixed(1) + 'K';
7
+ }
8
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
9
+ }
10
+
11
+ export function formatBytes(bytes) {
12
+ if (bytes === 0) return '0 B';
13
+ const k = 1024;
14
+ const sizes = ['B', 'KB', 'MB', 'GB'];
15
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
16
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
17
+ }
18
+
19
+ export function formatDuration(ms) {
20
+ if (ms < 1000) return `${ms}ms`;
21
+
22
+ const seconds = Math.floor(ms / 1000);
23
+ const minutes = Math.floor(seconds / 60);
24
+ const hours = Math.floor(minutes / 60);
25
+ const days = Math.floor(hours / 24);
26
+
27
+ if (days > 0) return `${days}d ${hours % 24}h`;
28
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
29
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
30
+ return `${seconds}s`;
31
+ }
32
+
33
+ export function formatDate(date) {
34
+ return new Date(date).toLocaleString('en-US', {
35
+ month: 'short',
36
+ day: 'numeric',
37
+ hour: '2-digit',
38
+ minute: '2-digit',
39
+ });
40
+ }
41
+
42
+ export function formatTime(date) {
43
+ return new Date(date).toLocaleTimeString('en-US', {
44
+ hour: '2-digit',
45
+ minute: '2-digit',
46
+ second: '2-digit',
47
+ });
48
+ }
49
+