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.
- package/API.md +1226 -0
- package/AUTH.md +2044 -0
- package/LICENSE.md +202 -0
- package/README.md +276 -53
- 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-dashboard-api.js +200 -0
- package/package.json +4 -2
- 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 +192 -16
- 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/advanced-pattern-tests.js +5 -5
- package/src/test/bus-mode-tests.js +24 -11
- package/src/test/edge-case-tests.js +12 -5
- package/src/test/enterprise-tests.js +48 -15
- package/src/test/utils.js +1 -1
- 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/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/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
|
+
};
|
package/dashboard/README.md
DELETED
|
@@ -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).
|
package/dashboard/index.html
DELETED
|
@@ -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>
|