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
package/WEBAPP.md ADDED
@@ -0,0 +1,1889 @@
1
+ # Queen Message Queue - Web Application Design
2
+
3
+ **Technology Stack:**
4
+ - Vue 3 (Composition API)
5
+ - JavaScript (ES6+)
6
+ - Tailwind CSS
7
+ - Chart.js with vue-chartjs
8
+ - Vue Router
9
+ - Axios for API calls
10
+
11
+ **Design Philosophy:**
12
+ - Clean, professional interface inspired by Kafka UI and modern analytics dashboards
13
+ - Real-time data from API endpoints (no mocked data)
14
+ - Responsive design with sidebar navigation
15
+ - Light and dark mode support
16
+ - Clear data visualization with meaningful charts
17
+ - Intuitive user interactions
18
+
19
+ ---
20
+
21
+ ## Table of Contents
22
+
23
+ 1. [Application Structure](#application-structure)
24
+ 2. [Design System](#design-system)
25
+ 3. [Pages & Components](#pages--components)
26
+ 4. [API Integration Map](#api-integration-map)
27
+ 5. [File Structure](#file-structure)
28
+ 6. [Implementation Plan](#implementation-plan)
29
+
30
+ ---
31
+
32
+ ## Application Structure
33
+
34
+ ### Core Layout
35
+
36
+ ```
37
+ ┌────────────────────────────────────────────────────┐
38
+ │ Header (Logo, Health Status, Theme Toggle) │
39
+ ├──────────┬─────────────────────────────────────────┤
40
+ │ │ │
41
+ │ Sidebar │ Main Content Area │
42
+ │ │ │
43
+ │ - Dashboard │
44
+ │ - Queues │ [Dynamic Page Content] │
45
+ │ - Consumer │
46
+ │ Groups │ │
47
+ │ - Messages │
48
+ │ - Analytics │
49
+ │ │ │
50
+ │ │ │
51
+ └──────────┴─────────────────────────────────────────┘
52
+ ```
53
+
54
+ ### Pages Overview
55
+
56
+ 1. **Dashboard** - System overview with key metrics
57
+ 2. **Queues** - Queue list and management
58
+ 3. **Queue Detail** - Individual queue deep-dive
59
+ 4. **Consumer Groups** - Consumer group monitoring
60
+ 5. **Messages** - Message browser and operations
61
+ 6. **Analytics** - Time-series analysis and insights
62
+
63
+ ---
64
+
65
+ ## Design System
66
+
67
+ ### Color Palette
68
+
69
+ **Light Mode:**
70
+ ```css
71
+ --bg-primary: #ffffff
72
+ --bg-secondary: #f9fafb
73
+ --bg-tertiary: #f3f4f6
74
+ --text-primary: #111827
75
+ --text-secondary: #6b7280
76
+ --text-tertiary: #9ca3af
77
+ --border-color: #e5e7eb
78
+ --accent-blue: #3b82f6
79
+ --accent-indigo: #6366f1
80
+ --accent-purple: #8b5cf6
81
+ --success: #10b981
82
+ --warning: #f59e0b
83
+ --danger: #ef4444
84
+ ```
85
+
86
+ **Dark Mode:**
87
+ ```css
88
+ --bg-primary: #0f172a
89
+ --bg-secondary: #1e293b
90
+ --bg-tertiary: #334155
91
+ --text-primary: #f1f5f9
92
+ --text-secondary: #cbd5e1
93
+ --text-tertiary: #94a3b8
94
+ --border-color: #334155
95
+ --accent-blue: #60a5fa
96
+ --accent-indigo: #818cf8
97
+ --accent-purple: #a78bfa
98
+ --success: #34d399
99
+ --warning: #fbbf24
100
+ --danger: #f87171
101
+ ```
102
+
103
+ ### Typography
104
+
105
+ ```css
106
+ --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif
107
+ --font-mono: 'JetBrains Mono', 'Courier New', monospace
108
+
109
+ --text-xs: 0.75rem (12px)
110
+ --text-sm: 0.875rem (14px)
111
+ --text-base: 1rem (16px)
112
+ --text-lg: 1.125rem (18px)
113
+ --text-xl: 1.25rem (20px)
114
+ --text-2xl: 1.5rem (24px)
115
+ --text-3xl: 1.875rem (30px)
116
+ ```
117
+
118
+ ### Spacing System
119
+
120
+ Following Tailwind's 4px base unit:
121
+ - `xs`: 4px
122
+ - `sm`: 8px
123
+ - `md`: 16px
124
+ - `lg`: 24px
125
+ - `xl`: 32px
126
+ - `2xl`: 48px
127
+
128
+ ### Component Patterns
129
+
130
+ **Cards:**
131
+ - White/dark background with subtle shadow
132
+ - Rounded corners (8px)
133
+ - Padding: 24px
134
+ - Border: 1px solid border-color
135
+
136
+ **Buttons:**
137
+ - Primary: Blue background, white text
138
+ - Secondary: Gray background, dark text
139
+ - Danger: Red background, white text
140
+ - Height: 40px, padding: 12px 24px
141
+ - Rounded: 6px
142
+
143
+ **Tables:**
144
+ - Alternating row colors
145
+ - Hover state on rows
146
+ - Sortable columns with indicators
147
+ - Sticky header
148
+
149
+ **Status Badges:**
150
+ - Rounded pill shape
151
+ - Color-coded: green (healthy/stable), yellow (warning), red (error)
152
+ - Small text with icon
153
+
154
+ ---
155
+
156
+ ## Pages & Components
157
+
158
+ ### 1. Dashboard (`/`)
159
+
160
+ **Purpose:** System overview showing health, throughput, and key metrics at a glance.
161
+
162
+ **API Routes Used:**
163
+ - `GET /health` - System health status
164
+ - `GET /metrics` - Performance metrics
165
+ - `GET /api/v1/resources/overview` - System-wide statistics
166
+ - `GET /api/v1/status` - Dashboard status with throughput
167
+
168
+ **Layout:**
169
+
170
+ ```
171
+ ┌─────────────────────────────────────────────────────────────────┐
172
+ │ Queen Message Queue ● Healthy 🌙 │
173
+ ├─────────────────────────────────────────────────────────────────┤
174
+ │ │
175
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────┐│
176
+ │ │ QUEUES │ │ PARTITIONS │ │ MESSAGES │ │ UPTIME ││
177
+ │ │ 53 │ │ 512 │ │ 200,000 │ │ 2h 15m ││
178
+ │ └──────────────┘ └──────────────┘ └──────────────┘ └─────────┘│
179
+ │ │
180
+ │ ┌──────────────────────────────────────────────────────────────┐│
181
+ │ │ Message Throughput (Last Hour) ││
182
+ │ │ ││
183
+ │ │ Chart: Line chart showing ingested/processed rates ││
184
+ │ │ X-axis: Time, Y-axis: Messages/second ││
185
+ │ │ Two lines: Ingested (blue), Processed (green) ││
186
+ │ │ ││
187
+ │ └──────────────────────────────────────────────────────────────┘│
188
+ │ │
189
+ │ ┌────────────────────────────┐ ┌───────────────────────────────┐│
190
+ │ │ Message Status │ │ System Performance ││
191
+ │ │ │ │ ││
192
+ │ │ Pending: 2 │ │ Requests/sec: 0.00 ││
193
+ │ │ Processing: 0 │ │ Messages/sec: 0.00 ││
194
+ │ │ Completed: 16,493 │ │ DB Connections: 3/3 ││
195
+ │ │ Failed: 0 │ │ Memory (Heap): 7.7 MB ││
196
+ │ │ Dead Letter: 0 │ │ CPU Time: 227ms ││
197
+ │ │ │ │ ││
198
+ │ └────────────────────────────┘ └───────────────────────────────┘│
199
+ │ │
200
+ │ ┌──────────────────────────────────────────────────────────────┐│
201
+ │ │ Top Queues by Activity ││
202
+ │ │ ││
203
+ │ │ Queue Name Pending Processing Completed ││
204
+ │ │ ───────────────────────────────────────────────────────────││
205
+ │ │ benchmark-queue-50 0 0 2,000 ││
206
+ │ │ benchmark-queue-49 0 0 2,000 ││
207
+ │ │ test-queue 2 0 3 ││
208
+ │ │ ││
209
+ │ └──────────────────────────────────────────────────────────────┘│
210
+ │ │
211
+ └─────────────────────────────────────────────────────────────────┘
212
+ ```
213
+
214
+ **Components:**
215
+
216
+ 1. **HealthIndicator.vue**
217
+ - Shows server health status from `/health`
218
+ - Color-coded dot: green (healthy), red (unhealthy)
219
+ - Displays uptime
220
+
221
+ 2. **MetricCard.vue**
222
+ - Reusable card for displaying single metrics
223
+ - Props: title, value, icon, trend (optional)
224
+ - Used for queues, partitions, messages, uptime
225
+
226
+ 3. **ThroughputChart.vue**
227
+ - Line chart using Chart.js
228
+ - Data from `/api/v1/status` throughput array
229
+ - Two datasets: ingested and processed messages
230
+
231
+ 4. **MessageStatusCard.vue**
232
+ - Displays message counts by status
233
+ - Data from `/api/v1/resources/overview`
234
+
235
+ 5. **PerformanceCard.vue**
236
+ - Shows system performance metrics
237
+ - Data from `/metrics` endpoint
238
+
239
+ 6. **TopQueuesTable.vue**
240
+ - Simple table showing top 5 queues
241
+ - Data from `/api/v1/resources/queues`
242
+ - Sortable by activity
243
+
244
+ ---
245
+
246
+ ### 2. Queues Page (`/queues`)
247
+
248
+ **Purpose:** List all queues with filtering, sorting, and management capabilities.
249
+
250
+ **API Routes Used:**
251
+ - `GET /api/v1/resources/queues` - Get all queues
252
+ - `GET /api/v1/resources/namespaces` - Get namespaces for filtering
253
+ - `POST /api/v1/configure` - Create new queue (modal)
254
+ - `DELETE /api/v1/resources/queues/:queue` - Delete queue
255
+
256
+ **Layout:**
257
+
258
+ ```
259
+ ┌─────────────────────────────────────────────────────────────────┐
260
+ │ Queues [+ New Queue] │
261
+ ├─────────────────────────────────────────────────────────────────┤
262
+ │ │
263
+ │ 🔍 Search queues... [Namespace ▼] [Clear Filters] │
264
+ │ │
265
+ │ ┌──────────────────────────────────────────────────────────────┐│
266
+ │ │ Queue Name ▲ Namespace Partitions Pending Processing Total││
267
+ │ ├──────────────────────────────────────────────────────────────┤│
268
+ │ │ test-queue - 1 2 0 0 ││
269
+ │ │ benchmark-q-50 benchmark 10 0 0 2,000 ││
270
+ │ │ benchmark-q-49 benchmark 10 0 0 2,000 ││
271
+ │ │ benchmark-q-48 benchmark 10 0 0 2,000 ││
272
+ │ │ ... ││
273
+ │ └──────────────────────────────────────────────────────────────┘│
274
+ │ │
275
+ │ Showing 1-20 of 53 [< 1 2 3 4 >] │
276
+ │ │
277
+ └─────────────────────────────────────────────────────────────────┘
278
+ ```
279
+
280
+ **Components:**
281
+
282
+ 1. **QueueList.vue**
283
+ - Main component for queues page
284
+ - Handles filtering, sorting, pagination
285
+ - Data from `/api/v1/resources/queues`
286
+
287
+ 2. **QueueRow.vue**
288
+ - Single row in the queue table
289
+ - Click to navigate to queue detail
290
+ - Shows queue statistics
291
+ - Action menu (delete, clear)
292
+
293
+ 3. **CreateQueueModal.vue**
294
+ - Modal form for creating new queue
295
+ - Calls `POST /api/v1/configure`
296
+ - Fields: name, namespace, task, ttl, priority, partition
297
+
298
+ 4. **FilterBar.vue**
299
+ - Search input
300
+ - Namespace dropdown (from `/api/v1/resources/namespaces`)
301
+ - Clear filters button
302
+
303
+ ---
304
+
305
+ ### 3. Queue Detail Page (`/queues/:queueName`)
306
+
307
+ **Purpose:** Deep dive into a single queue with partition details, messages, and performance.
308
+
309
+ **API Routes Used:**
310
+ - `GET /api/v1/resources/queues/:queue` - Queue details
311
+ - `GET /api/v1/status/queues/:queue` - Queue status with time range
312
+ - `GET /api/v1/status/queues/:queue/messages` - Queue messages
313
+ - `DELETE /api/v1/queues/:queue/clear` - Clear queue
314
+ - `POST /api/v1/push` - Push message to queue
315
+
316
+ **Layout:**
317
+
318
+ ```
319
+ ┌─────────────────────────────────────────────────────────────────┐
320
+ │ ← Queues / test-queue [Clear Queue] │
321
+ ├─────────────────────────────────────────────────────────────────┤
322
+ │ │
323
+ │ test-queue Created: Oct 15, 2025 │
324
+ │ Namespace: - Task: - Priority: 0 │
325
+ │ │
326
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────┐│
327
+ │ │ PENDING │ │ PROCESSING │ │ COMPLETED │ │ FAILED ││
328
+ │ │ 0 │ │ 0 │ │ 3 │ │ 0 ││
329
+ │ └──────────────┘ └──────────────┘ └──────────────┘ └─────────┘│
330
+ │ │
331
+ │ ┌──────────────────────────────────────────────────────────────┐│
332
+ │ │ Queue Configuration ││
333
+ │ │ ││
334
+ │ │ Lease Time: 300s Max Queue Size: 10,000 ││
335
+ │ │ TTL: 3,600s Retry Limit: 3 ││
336
+ │ │ Retry Delay: 1,000ms DLQ Enabled: No ││
337
+ │ │ ││
338
+ │ └──────────────────────────────────────────────────────────────┘│
339
+ │ │
340
+ │ ┌──────────────────────────────────────────────────────────────┐│
341
+ │ │ Partitions [Push Message] ││
342
+ │ ├──────────────────────────────────────────────────────────────┤│
343
+ │ │ Default ││
344
+ │ │ Pending: 0 Processing: 0 Completed: 3 Failed: 0 ││
345
+ │ │ Last Activity: Oct 15, 06:22:21 ││
346
+ │ │ Consumed: 3 messages in 2 batches ││
347
+ │ └──────────────────────────────────────────────────────────────┘│
348
+ │ │
349
+ │ ┌──────────────────────────────────────────────────────────────┐│
350
+ │ │ Recent Messages ││
351
+ │ ├──────────────────────────────────────────────────────────────┤│
352
+ │ │ Transaction ID Created Status ││
353
+ │ │ ────────────────────────────────────────────────────────────││
354
+ │ │ 0199e688-1857... 06:21:42 Completed ││
355
+ │ │ 0199e688-4d29... 06:22:01 Completed ││
356
+ │ │ ││
357
+ │ └──────────────────────────────────────────────────────────────┘│
358
+ │ │
359
+ └─────────────────────────────────────────────────────────────────┘
360
+ ```
361
+
362
+ **Components:**
363
+
364
+ 1. **QueueDetailHeader.vue**
365
+ - Queue name and metadata
366
+ - Back button
367
+ - Action buttons (clear, delete)
368
+
369
+ 2. **QueueMetrics.vue**
370
+ - Four metric cards for message status
371
+ - Data from `/api/v1/status/queues/:queue`
372
+
373
+ 3. **QueueConfig.vue**
374
+ - Display queue configuration
375
+ - Grid layout for config values
376
+
377
+ 4. **PartitionList.vue**
378
+ - List of partitions with stats
379
+ - Data from queue detail response
380
+
381
+ 5. **RecentMessages.vue**
382
+ - Table of recent messages
383
+ - Data from `/api/v1/status/queues/:queue/messages`
384
+ - Link to full message view
385
+
386
+ 6. **PushMessageModal.vue**
387
+ - Form to push new message
388
+ - JSON editor for payload
389
+ - Calls `POST /api/v1/push`
390
+
391
+ ---
392
+
393
+ ### 4. Consumer Groups Page (`/consumer-groups`)
394
+
395
+ **Purpose:** Monitor consumer groups, lag, and consumption patterns.
396
+
397
+ **API Routes Used:**
398
+ - `GET /api/v1/resources/queues` - Get queues with consumer info
399
+ - `GET /api/v1/status/queues` - Get detailed consumer stats
400
+
401
+ **Layout:**
402
+
403
+ ```
404
+ ┌─────────────────────────────────────────────────────────────────┐
405
+ │ Consumer Groups │
406
+ ├─────────────────────────────────────────────────────────────────┤
407
+ │ │
408
+ │ 🔍 Search groups... [Status ▼] [Clear Filters] │
409
+ │ │
410
+ │ ┌──────────────────────────────────────────────────────────────┐│
411
+ │ │ Group Name ▲ Topics Offset Lag Time Lag State││
412
+ │ ├──────────────────────────────────────────────────────────────┤│
413
+ │ │ worker-group-1 test-queue 0 0ms ● Stable││
414
+ │ │ consumer-default benchmark-q-01 1,234 5m 23s ● Stable││
415
+ │ │ batch-processor batch-queue 45 12s ● Stable││
416
+ │ │ ... ││
417
+ │ └──────────────────────────────────────────────────────────────┘│
418
+ │ │
419
+ └─────────────────────────────────────────────────────────────────┘
420
+ ```
421
+
422
+ **Components:**
423
+
424
+ 1. **ConsumerGroupList.vue**
425
+ - Table of consumer groups
426
+ - Derived from queue data showing active consumers
427
+ - Filtering and sorting
428
+
429
+ 2. **ConsumerGroupRow.vue**
430
+ - Single consumer group entry
431
+ - Status badge
432
+ - Lag indicators
433
+
434
+ ---
435
+
436
+ ### 5. Messages Page (`/messages`)
437
+
438
+ **Purpose:** Browse, search, and manage individual messages across all queues.
439
+
440
+ **API Routes Used:**
441
+ - `GET /api/v1/messages` - List messages with filters
442
+ - `GET /api/v1/messages/:transactionId` - Get message detail
443
+ - `DELETE /api/v1/messages/:transactionId` - Delete message
444
+ - `POST /api/v1/messages/:transactionId/retry` - Retry message
445
+ - `POST /api/v1/messages/:transactionId/dlq` - Move to DLQ
446
+
447
+ **Layout:**
448
+
449
+ ```
450
+ ┌─────────────────────────────────────────────────────────────────┐
451
+ │ Messages │
452
+ ├─────────────────────────────────────────────────────────────────┤
453
+ │ │
454
+ │ 🔍 Search by ID... [Queue ▼] [Status ▼] [Clear Filters] │
455
+ │ │
456
+ │ ┌──────────────────────────────────────────────────────────────┐│
457
+ │ │ Transaction ID Queue Created Status ││
458
+ │ ├──────────────────────────────────────────────────────────────┤│
459
+ │ │ 0199e688-1857... test-queue Oct 15 06:21 Completed ││
460
+ │ │ 0199e688-4d29... test-queue Oct 15 06:22 Completed ││
461
+ │ │ ... ││
462
+ │ └──────────────────────────────────────────────────────────────┘│
463
+ │ │
464
+ │ [Message Detail Panel appears on row click] │
465
+ │ │
466
+ └─────────────────────────────────────────────────────────────────┘
467
+ ```
468
+
469
+ **Components:**
470
+
471
+ 1. **MessageList.vue**
472
+ - Table of messages
473
+ - Click to show detail panel
474
+ - Pagination
475
+
476
+ 2. **MessageDetailPanel.vue**
477
+ - Slide-out panel from right
478
+ - Shows full message data
479
+ - JSON payload viewer
480
+ - Action buttons (retry, delete, move to DLQ)
481
+
482
+ 3. **MessageFilters.vue**
483
+ - Filter bar with multiple options
484
+ - Queue selector
485
+ - Status selector
486
+
487
+ ---
488
+
489
+ ### 6. Analytics Page (`/analytics`)
490
+
491
+ **Purpose:** Time-series analysis, trends, and system insights.
492
+
493
+ **API Routes Used:**
494
+ - `GET /api/v1/status/analytics` - Analytics data with time intervals
495
+ - `GET /api/v1/status` - Status data for time range
496
+
497
+ **Layout:**
498
+
499
+ ```
500
+ ┌─────────────────────────────────────────────────────────────────┐
501
+ │ Analytics │
502
+ ├─────────────────────────────────────────────────────────────────┤
503
+ │ │
504
+ │ [Last Hour ▼] [All Queues ▼] [Refresh] │
505
+ │ │
506
+ │ ┌──────────────────────────────────────────────────────────────┐│
507
+ │ │ Message Flow (Time Series) ││
508
+ │ │ ││
509
+ │ │ Stacked Area Chart: ││
510
+ │ │ - Ingested (blue area) ││
511
+ │ │ - Processed (green area) ││
512
+ │ │ - Failed (red line) ││
513
+ │ │ ││
514
+ │ └──────────────────────────────────────────────────────────────┘│
515
+ │ │
516
+ │ ┌────────────────────────────┐ ┌───────────────────────────────┐│
517
+ │ │ Top Queues by Volume │ │ Message Status Distribution ││
518
+ │ │ │ │ ││
519
+ │ │ Bar Chart: │ │ Doughnut Chart: ││
520
+ │ │ Queue names on X-axis │ │ - Pending (yellow) ││
521
+ │ │ Message count on Y-axis │ │ - Processing (blue) ││
522
+ │ │ │ │ - Completed (green) ││
523
+ │ │ │ │ - Failed (red) ││
524
+ │ │ │ │ - DLQ (gray) ││
525
+ │ │ │ │ ││
526
+ │ └────────────────────────────┘ └───────────────────────────────┘│
527
+ │ │
528
+ │ ┌──────────────────────────────────────────────────────────────┐│
529
+ │ │ Performance Metrics ││
530
+ │ │ ││
531
+ │ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ││
532
+ │ │ │ Avg Latency │ │ Throughput │ │ Error Rate │ ││
533
+ │ │ │ 125ms │ │ 1,234 msg/s │ │ 0.01% │ ││
534
+ │ │ └────────────────┘ └────────────────┘ └────────────────┘ ││
535
+ │ │ ││
536
+ │ └──────────────────────────────────────────────────────────────┘│
537
+ │ │
538
+ └─────────────────────────────────────────────────────────────────┘
539
+ ```
540
+
541
+ **Components:**
542
+
543
+ 1. **TimeRangeSelector.vue**
544
+ - Dropdown for time range selection
545
+ - Options: Last hour, 6 hours, 24 hours, 7 days
546
+
547
+ 2. **MessageFlowChart.vue**
548
+ - Stacked area or line chart
549
+ - Data from `/api/v1/status/analytics`
550
+
551
+ 3. **TopQueuesChart.vue**
552
+ - Horizontal bar chart
553
+ - Shows top queues by message volume
554
+
555
+ 4. **MessageDistributionChart.vue**
556
+ - Doughnut chart showing status distribution
557
+ - Color-coded segments
558
+
559
+ 5. **PerformanceMetrics.vue**
560
+ - Three metric cards
561
+ - Calculated from analytics data
562
+
563
+ ---
564
+
565
+ ## API Integration Map
566
+
567
+ ### Component to API Endpoint Mapping
568
+
569
+ | Component | API Endpoint | Method | Purpose |
570
+ |-----------|-------------|--------|---------|
571
+ | HealthIndicator | `/health` | GET | Server health check |
572
+ | MetricCard (Dashboard) | `/api/v1/resources/overview` | GET | System metrics |
573
+ | ThroughputChart | `/api/v1/status` | GET | Throughput data |
574
+ | PerformanceCard | `/metrics` | GET | System performance |
575
+ | QueueList | `/api/v1/resources/queues` | GET | List all queues |
576
+ | QueueList | `/api/v1/resources/namespaces` | GET | Filter namespaces |
577
+ | CreateQueueModal | `/api/v1/configure` | POST | Create queue |
578
+ | QueueRow | `/api/v1/resources/queues/:queue` | DELETE | Delete queue |
579
+ | QueueDetailHeader | `/api/v1/resources/queues/:queue` | GET | Queue details |
580
+ | QueueMetrics | `/api/v1/status/queues/:queue` | GET | Queue status |
581
+ | RecentMessages | `/api/v1/status/queues/:queue/messages` | GET | Queue messages |
582
+ | PushMessageModal | `/api/v1/push` | POST | Push message |
583
+ | ClearQueueAction | `/api/v1/queues/:queue/clear` | DELETE | Clear queue |
584
+ | MessageList | `/api/v1/messages` | GET | List messages |
585
+ | MessageDetailPanel | `/api/v1/messages/:transactionId` | GET | Message detail |
586
+ | MessageDetailPanel | `/api/v1/messages/:transactionId` | DELETE | Delete message |
587
+ | MessageDetailPanel | `/api/v1/messages/:transactionId/retry` | POST | Retry message |
588
+ | MessageDetailPanel | `/api/v1/messages/:transactionId/dlq` | POST | Move to DLQ |
589
+ | ConsumerGroupList | `/api/v1/resources/queues` | GET | Consumer info |
590
+ | MessageFlowChart | `/api/v1/status/analytics` | GET | Analytics data |
591
+ | TopQueuesChart | `/api/v1/status/analytics` | GET | Queue metrics |
592
+
593
+ ---
594
+
595
+ ## File Structure
596
+
597
+ ```
598
+ webapp/
599
+ ├── public/
600
+ │ ├── index.html
601
+ │ └── assets/
602
+ │ ├── queen-logo.svg
603
+ │ ├── queen-logo-blue.svg
604
+ │ ├── queen-logo-cyan.svg
605
+ │ ├── queen-logo-indigo.svg
606
+ │ ├── queen-logo-orange.svg
607
+ │ ├── queen-logo-pink.svg
608
+ │ ├── queen-logo-purple.svg
609
+ │ ├── queen-logo-rose.svg
610
+ │ └── favicon.svg
611
+ │
612
+ ├── src/
613
+ │ ├── main.js # App entry point
614
+ │ ├── App.vue # Root component with layout
615
+ │ │
616
+ │ ├── router/
617
+ │ │ └── index.js # Vue Router configuration
618
+ │ │
619
+ │ ├── api/
620
+ │ │ ├── client.js # Axios instance with base config
621
+ │ │ ├── health.js # Health & metrics endpoints
622
+ │ │ ├── queues.js # Queue management endpoints
623
+ │ │ ├── messages.js # Message operations endpoints
624
+ │ │ ├── resources.js # Resource query endpoints
625
+ │ │ └── analytics.js # Analytics endpoints
626
+ │ │
627
+ │ ├── composables/
628
+ │ │ ├── useTheme.js # Dark mode toggle
629
+ │ │ ├── useApi.js # API call wrapper with error handling
630
+ │ │ └── usePolling.js # Manual refresh helper
631
+ │ │
632
+ │ ├── components/
633
+ │ │ ├── layout/
634
+ │ │ │ ├── AppLayout.vue # Main layout with sidebar
635
+ │ │ │ ├── AppHeader.vue # Header with logo and controls
636
+ │ │ │ ├── AppSidebar.vue # Sidebar navigation
637
+ │ │ │ └── ThemeToggle.vue # Light/dark theme switcher
638
+ │ │ │
639
+ │ │ ├── common/
640
+ │ │ │ ├── MetricCard.vue # Reusable metric display card
641
+ │ │ │ ├── StatusBadge.vue # Status indicator badge
642
+ │ │ │ ├── DataTable.vue # Generic data table
643
+ │ │ │ ├── LoadingSpinner.vue # Loading state
644
+ │ │ │ ├── EmptyState.vue # Empty state placeholder
645
+ │ │ │ ├── ConfirmDialog.vue # Confirmation modal
646
+ │ │ │ └── RefreshButton.vue # Manual refresh button
647
+ │ │ │
648
+ │ │ ├── dashboard/
649
+ │ │ │ ├── HealthIndicator.vue
650
+ │ │ │ ├── ThroughputChart.vue
651
+ │ │ │ ├── MessageStatusCard.vue
652
+ │ │ │ ├── PerformanceCard.vue
653
+ │ │ │ └── TopQueuesTable.vue
654
+ │ │ │
655
+ │ │ ├── queues/
656
+ │ │ │ ├── QueueList.vue
657
+ │ │ │ ├── QueueRow.vue
658
+ │ │ │ ├── QueueFilters.vue
659
+ │ │ │ └── CreateQueueModal.vue
660
+ │ │ │
661
+ │ │ ├── queue-detail/
662
+ │ │ │ ├── QueueDetailHeader.vue
663
+ │ │ │ ├── QueueMetrics.vue
664
+ │ │ │ ├── QueueConfig.vue
665
+ │ │ │ ├── PartitionList.vue
666
+ │ │ │ ├── RecentMessages.vue
667
+ │ │ │ └── PushMessageModal.vue
668
+ │ │ │
669
+ │ │ ├── messages/
670
+ │ │ │ ├── MessageList.vue
671
+ │ │ │ ├── MessageDetailPanel.vue
672
+ │ │ │ ├── MessageFilters.vue
673
+ │ │ │ └── JsonViewer.vue
674
+ │ │ │
675
+ │ │ ├── consumer-groups/
676
+ │ │ │ ├── ConsumerGroupList.vue
677
+ │ │ │ └── ConsumerGroupRow.vue
678
+ │ │ │
679
+ │ │ └── analytics/
680
+ │ │ ├── TimeRangeSelector.vue
681
+ │ │ ├── MessageFlowChart.vue
682
+ │ │ ├── TopQueuesChart.vue
683
+ │ │ ├── MessageDistributionChart.vue
684
+ │ │ └── PerformanceMetrics.vue
685
+ │ │
686
+ │ ├── views/
687
+ │ │ ├── Dashboard.vue # Dashboard page
688
+ │ │ ├── Queues.vue # Queues list page
689
+ │ │ ├── QueueDetail.vue # Queue detail page
690
+ │ │ ├── Messages.vue # Messages page
691
+ │ │ ├── ConsumerGroups.vue # Consumer groups page
692
+ │ │ └── Analytics.vue # Analytics page
693
+ │ │
694
+ │ ├── utils/
695
+ │ │ ├── formatters.js # Number, date, size formatters
696
+ │ │ ├── constants.js # App constants
697
+ │ │ └── colors.js # Color scheme definitions
698
+ │ │
699
+ │ └── assets/
700
+ │ └── styles/
701
+ │ ├── main.css # Global styles
702
+ │ └── tailwind.css # Tailwind imports
703
+ │
704
+ ├── package.json
705
+ ├── tailwind.config.js
706
+ ├── vite.config.js
707
+ ├── postcss.config.js
708
+ └── README.md
709
+ ```
710
+
711
+ ---
712
+
713
+ ## Implementation Plan
714
+
715
+ ### Phase 1: Project Setup & Core Infrastructure (Day 1)
716
+
717
+ **1.1 Initialize Project**
718
+ ```bash
719
+ npm create vite@latest webapp -- --template vue
720
+ cd webapp
721
+ npm install
722
+ ```
723
+
724
+ **1.2 Install Dependencies**
725
+ ```bash
726
+ npm install vue-router axios chart.js vue-chartjs
727
+ npm install -D tailwindcss postcss autoprefixer
728
+ npx tailwindcss init -p
729
+ ```
730
+
731
+ **1.3 Configure Tailwind CSS**
732
+
733
+ `tailwind.config.js`:
734
+ ```javascript
735
+ export default {
736
+ content: [
737
+ "./index.html",
738
+ "./src/**/*.{vue,js}",
739
+ ],
740
+ darkMode: 'class',
741
+ theme: {
742
+ extend: {
743
+ colors: {
744
+ 'queen-blue': '#3b82f6',
745
+ 'queen-indigo': '#6366f1',
746
+ 'queen-purple': '#8b5cf6',
747
+ },
748
+ fontFamily: {
749
+ sans: ['Inter', 'sans-serif'],
750
+ mono: ['JetBrains Mono', 'monospace'],
751
+ },
752
+ },
753
+ },
754
+ plugins: [],
755
+ }
756
+ ```
757
+
758
+ **1.4 Setup Base Files**
759
+
760
+ `src/assets/styles/main.css`:
761
+ ```css
762
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
763
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');
764
+
765
+ @tailwind base;
766
+ @tailwind components;
767
+ @tailwind utilities;
768
+
769
+ @layer base {
770
+ * {
771
+ @apply border-gray-200 dark:border-gray-700;
772
+ }
773
+
774
+ body {
775
+ @apply bg-white dark:bg-slate-900 text-gray-900 dark:text-gray-100;
776
+ }
777
+ }
778
+
779
+ @layer components {
780
+ .card {
781
+ @apply bg-white dark:bg-slate-800 rounded-lg shadow-sm border p-6;
782
+ }
783
+
784
+ .btn {
785
+ @apply px-6 py-2.5 rounded-md font-medium transition-colors;
786
+ }
787
+
788
+ .btn-primary {
789
+ @apply bg-blue-600 hover:bg-blue-700 text-white;
790
+ }
791
+
792
+ .btn-secondary {
793
+ @apply bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100;
794
+ }
795
+
796
+ .btn-danger {
797
+ @apply bg-red-600 hover:bg-red-700 text-white;
798
+ }
799
+
800
+ .badge {
801
+ @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
802
+ }
803
+
804
+ .badge-success {
805
+ @apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200;
806
+ }
807
+
808
+ .badge-warning {
809
+ @apply bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200;
810
+ }
811
+
812
+ .badge-danger {
813
+ @apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200;
814
+ }
815
+
816
+ .input {
817
+ @apply w-full px-4 py-2 border rounded-md bg-white dark:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500;
818
+ }
819
+ }
820
+ ```
821
+
822
+ **1.5 Create API Client**
823
+
824
+ `src/api/client.js`:
825
+ ```javascript
826
+ import axios from 'axios';
827
+
828
+ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:6632';
829
+
830
+ const apiClient = axios.create({
831
+ baseURL: API_BASE_URL,
832
+ timeout: 30000,
833
+ headers: {
834
+ 'Content-Type': 'application/json',
835
+ },
836
+ });
837
+
838
+ // Response interceptor for error handling
839
+ apiClient.interceptors.response.use(
840
+ (response) => response,
841
+ (error) => {
842
+ if (error.response) {
843
+ // Server responded with error
844
+ console.error('API Error:', error.response.data);
845
+ } else if (error.request) {
846
+ // Request made but no response
847
+ console.error('Network Error:', error.message);
848
+ } else {
849
+ console.error('Error:', error.message);
850
+ }
851
+ return Promise.reject(error);
852
+ }
853
+ );
854
+
855
+ export default apiClient;
856
+ ```
857
+
858
+ **1.6 Create API Modules**
859
+
860
+ `src/api/health.js`:
861
+ ```javascript
862
+ import apiClient from './client';
863
+
864
+ export const healthApi = {
865
+ getHealth: () => apiClient.get('/health'),
866
+ getMetrics: () => apiClient.get('/metrics'),
867
+ };
868
+ ```
869
+
870
+ `src/api/queues.js`:
871
+ ```javascript
872
+ import apiClient from './client';
873
+
874
+ export const queuesApi = {
875
+ getQueues: (params) => apiClient.get('/api/v1/resources/queues', { params }),
876
+ getQueue: (queueName) => apiClient.get(`/api/v1/resources/queues/${queueName}`),
877
+ deleteQueue: (queueName) => apiClient.delete(`/api/v1/resources/queues/${queueName}`),
878
+ clearQueue: (queueName, partition) => {
879
+ const params = partition ? { partition } : {};
880
+ return apiClient.delete(`/api/v1/queues/${queueName}/clear`, { params });
881
+ },
882
+ configureQueue: (data) => apiClient.post('/api/v1/configure', data),
883
+
884
+ // Partition operations
885
+ getPartitions: (params) => apiClient.get('/api/v1/resources/partitions', { params }),
886
+ };
887
+ ```
888
+
889
+ `src/api/messages.js`:
890
+ ```javascript
891
+ import apiClient from './client';
892
+
893
+ export const messagesApi = {
894
+ getMessages: (params) => apiClient.get('/api/v1/messages', { params }),
895
+ getMessage: (transactionId) => apiClient.get(`/api/v1/messages/${transactionId}`),
896
+ deleteMessage: (transactionId) => apiClient.delete(`/api/v1/messages/${transactionId}`),
897
+ retryMessage: (transactionId) => apiClient.post(`/api/v1/messages/${transactionId}/retry`),
898
+ moveToDLQ: (transactionId) => apiClient.post(`/api/v1/messages/${transactionId}/dlq`),
899
+ getRelatedMessages: (transactionId) => apiClient.get(`/api/v1/messages/${transactionId}/related`),
900
+
901
+ // Push messages
902
+ pushMessages: (data) => apiClient.post('/api/v1/push', data),
903
+
904
+ // Pop messages
905
+ popMessages: (queue, partition, params) => {
906
+ if (partition) {
907
+ return apiClient.get(`/api/v1/pop/queue/${queue}/partition/${partition}`, { params });
908
+ }
909
+ return apiClient.get(`/api/v1/pop/queue/${queue}`, { params });
910
+ },
911
+
912
+ // Acknowledgment
913
+ ackMessage: (data) => apiClient.post('/api/v1/ack', data),
914
+ batchAck: (data) => apiClient.post('/api/v1/ack/batch', data),
915
+ };
916
+ ```
917
+
918
+ `src/api/resources.js`:
919
+ ```javascript
920
+ import apiClient from './client';
921
+
922
+ export const resourcesApi = {
923
+ getOverview: () => apiClient.get('/api/v1/resources/overview'),
924
+ getNamespaces: () => apiClient.get('/api/v1/resources/namespaces'),
925
+ getTasks: () => apiClient.get('/api/v1/resources/tasks'),
926
+ };
927
+ ```
928
+
929
+ `src/api/analytics.js`:
930
+ ```javascript
931
+ import apiClient from './client';
932
+
933
+ export const analyticsApi = {
934
+ getStatus: (params) => apiClient.get('/api/v1/status', { params }),
935
+ getQueues: (params) => apiClient.get('/api/v1/status/queues', { params }),
936
+ getQueueDetail: (queueName, params) => apiClient.get(`/api/v1/status/queues/${queueName}`, { params }),
937
+ getQueueMessages: (queueName, params) => apiClient.get(`/api/v1/status/queues/${queueName}/messages`, { params }),
938
+ getAnalytics: (params) => apiClient.get('/api/v1/status/analytics', { params }),
939
+ };
940
+ ```
941
+
942
+ ---
943
+
944
+ ### Phase 2: Layout & Navigation (Day 1-2)
945
+
946
+ **2.1 Create Layout Components**
947
+
948
+ `src/App.vue`:
949
+ ```vue
950
+ <template>
951
+ <div :class="{ 'dark': isDark }" class="min-h-screen">
952
+ <AppLayout>
953
+ <router-view />
954
+ </AppLayout>
955
+ </div>
956
+ </template>
957
+
958
+ <script setup>
959
+ import { provide } from 'vue';
960
+ import AppLayout from './components/layout/AppLayout.vue';
961
+ import { useTheme } from './composables/useTheme';
962
+
963
+ const { isDark, toggleTheme } = useTheme();
964
+
965
+ // Provide theme globally
966
+ provide('theme', { isDark, toggleTheme });
967
+ </script>
968
+ ```
969
+
970
+ `src/components/layout/AppLayout.vue`:
971
+ ```vue
972
+ <template>
973
+ <div class="flex h-screen bg-gray-50 dark:bg-slate-900">
974
+ <AppSidebar />
975
+
976
+ <div class="flex-1 flex flex-col overflow-hidden">
977
+ <AppHeader />
978
+
979
+ <main class="flex-1 overflow-y-auto p-6">
980
+ <slot />
981
+ </main>
982
+ </div>
983
+ </div>
984
+ </template>
985
+
986
+ <script setup>
987
+ import AppHeader from './AppHeader.vue';
988
+ import AppSidebar from './AppSidebar.vue';
989
+ </script>
990
+ ```
991
+
992
+ `src/components/layout/AppHeader.vue`:
993
+ ```vue
994
+ <template>
995
+ <header class="bg-white dark:bg-slate-800 border-b h-16 flex items-center px-6 justify-between">
996
+ <div class="flex items-center gap-4">
997
+ <img src="/assets/queen-logo.svg" alt="Queen" class="h-8 w-8" />
998
+ <h1 class="text-xl font-semibold">Queen Message Queue</h1>
999
+ </div>
1000
+
1001
+ <div class="flex items-center gap-4">
1002
+ <HealthIndicator />
1003
+ <ThemeToggle />
1004
+ </div>
1005
+ </header>
1006
+ </template>
1007
+
1008
+ <script setup>
1009
+ import HealthIndicator from '../dashboard/HealthIndicator.vue';
1010
+ import ThemeToggle from './ThemeToggle.vue';
1011
+ </script>
1012
+ ```
1013
+
1014
+ `src/components/layout/AppSidebar.vue`:
1015
+ ```vue
1016
+ <template>
1017
+ <aside class="w-64 bg-white dark:bg-slate-800 border-r flex flex-col">
1018
+ <nav class="flex-1 p-4 space-y-1">
1019
+ <router-link
1020
+ v-for="item in navigation"
1021
+ :key="item.path"
1022
+ :to="item.path"
1023
+ class="flex items-center gap-3 px-4 py-3 rounded-lg transition-colors"
1024
+ :class="[
1025
+ isActive(item.path)
1026
+ ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400'
1027
+ : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate-700'
1028
+ ]"
1029
+ >
1030
+ <component :is="item.icon" class="w-5 h-5" />
1031
+ <span class="font-medium">{{ item.name }}</span>
1032
+ </router-link>
1033
+ </nav>
1034
+ </aside>
1035
+ </template>
1036
+
1037
+ <script setup>
1038
+ import { useRoute } from 'vue-router';
1039
+
1040
+ const route = useRoute();
1041
+
1042
+ const navigation = [
1043
+ { name: 'Dashboard', path: '/', icon: 'HomeIcon' },
1044
+ { name: 'Queues', path: '/queues', icon: 'QueueIcon' },
1045
+ { name: 'Consumer Groups', path: '/consumer-groups', icon: 'GroupIcon' },
1046
+ { name: 'Messages', path: '/messages', icon: 'MessageIcon' },
1047
+ { name: 'Analytics', path: '/analytics', icon: 'ChartIcon' },
1048
+ ];
1049
+
1050
+ const isActive = (path) => {
1051
+ if (path === '/') return route.path === '/';
1052
+ return route.path.startsWith(path);
1053
+ };
1054
+ </script>
1055
+ ```
1056
+
1057
+ `src/components/layout/ThemeToggle.vue`:
1058
+ ```vue
1059
+ <template>
1060
+ <button
1061
+ @click="toggleTheme"
1062
+ class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-slate-700"
1063
+ aria-label="Toggle theme"
1064
+ >
1065
+ <svg v-if="isDark" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
1066
+ <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"/>
1067
+ </svg>
1068
+ <svg v-else class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
1069
+ <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
1070
+ </svg>
1071
+ </button>
1072
+ </template>
1073
+
1074
+ <script setup>
1075
+ import { inject } from 'vue';
1076
+
1077
+ const { isDark, toggleTheme } = inject('theme');
1078
+ </script>
1079
+ ```
1080
+
1081
+ **2.2 Create Composables**
1082
+
1083
+ `src/composables/useTheme.js`:
1084
+ ```javascript
1085
+ import { ref, watch, onMounted } from 'vue';
1086
+
1087
+ export function useTheme() {
1088
+ const isDark = ref(false);
1089
+
1090
+ const toggleTheme = () => {
1091
+ isDark.value = !isDark.value;
1092
+ };
1093
+
1094
+ // Watch for changes and update localStorage and document class
1095
+ watch(isDark, (newValue) => {
1096
+ if (newValue) {
1097
+ document.documentElement.classList.add('dark');
1098
+ localStorage.setItem('theme', 'dark');
1099
+ } else {
1100
+ document.documentElement.classList.remove('dark');
1101
+ localStorage.setItem('theme', 'light');
1102
+ }
1103
+ });
1104
+
1105
+ // Initialize theme from localStorage or system preference
1106
+ onMounted(() => {
1107
+ const savedTheme = localStorage.getItem('theme');
1108
+ if (savedTheme) {
1109
+ isDark.value = savedTheme === 'dark';
1110
+ } else {
1111
+ isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
1112
+ }
1113
+ });
1114
+
1115
+ return {
1116
+ isDark,
1117
+ toggleTheme,
1118
+ };
1119
+ }
1120
+ ```
1121
+
1122
+ `src/composables/useApi.js`:
1123
+ ```javascript
1124
+ import { ref } from 'vue';
1125
+
1126
+ export function useApi(apiCall) {
1127
+ const data = ref(null);
1128
+ const error = ref(null);
1129
+ const loading = ref(false);
1130
+
1131
+ const execute = async (...params) => {
1132
+ loading.value = true;
1133
+ error.value = null;
1134
+
1135
+ try {
1136
+ const response = await apiCall(...params);
1137
+ data.value = response.data;
1138
+ return response.data;
1139
+ } catch (err) {
1140
+ error.value = err.response?.data?.error || err.message || 'An error occurred';
1141
+ throw err;
1142
+ } finally {
1143
+ loading.value = false;
1144
+ }
1145
+ };
1146
+
1147
+ const refresh = () => execute();
1148
+
1149
+ return {
1150
+ data,
1151
+ error,
1152
+ loading,
1153
+ execute,
1154
+ refresh,
1155
+ };
1156
+ }
1157
+ ```
1158
+
1159
+ **2.3 Create Router**
1160
+
1161
+ `src/router/index.js`:
1162
+ ```javascript
1163
+ import { createRouter, createWebHistory } from 'vue-router';
1164
+
1165
+ const routes = [
1166
+ {
1167
+ path: '/',
1168
+ name: 'Dashboard',
1169
+ component: () => import('../views/Dashboard.vue'),
1170
+ },
1171
+ {
1172
+ path: '/queues',
1173
+ name: 'Queues',
1174
+ component: () => import('../views/Queues.vue'),
1175
+ },
1176
+ {
1177
+ path: '/queues/:queueName',
1178
+ name: 'QueueDetail',
1179
+ component: () => import('../views/QueueDetail.vue'),
1180
+ },
1181
+ {
1182
+ path: '/consumer-groups',
1183
+ name: 'ConsumerGroups',
1184
+ component: () => import('../views/ConsumerGroups.vue'),
1185
+ },
1186
+ {
1187
+ path: '/messages',
1188
+ name: 'Messages',
1189
+ component: () => import('../views/Messages.vue'),
1190
+ },
1191
+ {
1192
+ path: '/analytics',
1193
+ name: 'Analytics',
1194
+ component: () => import('../views/Analytics.vue'),
1195
+ },
1196
+ ];
1197
+
1198
+ const router = createRouter({
1199
+ history: createWebHistory(),
1200
+ routes,
1201
+ });
1202
+
1203
+ export default router;
1204
+ ```
1205
+
1206
+ ---
1207
+
1208
+ ### Phase 3: Common Components (Day 2)
1209
+
1210
+ **3.1 Utility Components**
1211
+
1212
+ `src/components/common/MetricCard.vue`:
1213
+ ```vue
1214
+ <template>
1215
+ <div class="card">
1216
+ <div class="flex items-center justify-between">
1217
+ <div>
1218
+ <p class="text-sm text-gray-500 dark:text-gray-400">{{ title }}</p>
1219
+ <p class="text-3xl font-bold mt-1">{{ formattedValue }}</p>
1220
+ <p v-if="subtitle" class="text-sm text-gray-500 dark:text-gray-400 mt-1">
1221
+ {{ subtitle }}
1222
+ </p>
1223
+ </div>
1224
+ <div v-if="icon" class="text-blue-500">
1225
+ <component :is="icon" class="w-10 h-10" />
1226
+ </div>
1227
+ </div>
1228
+ </div>
1229
+ </template>
1230
+
1231
+ <script setup>
1232
+ import { computed } from 'vue';
1233
+ import { formatNumber } from '../../utils/formatters';
1234
+
1235
+ const props = defineProps({
1236
+ title: String,
1237
+ value: [String, Number],
1238
+ subtitle: String,
1239
+ icon: Object,
1240
+ });
1241
+
1242
+ const formattedValue = computed(() => {
1243
+ if (typeof props.value === 'number') {
1244
+ return formatNumber(props.value);
1245
+ }
1246
+ return props.value;
1247
+ });
1248
+ </script>
1249
+ ```
1250
+
1251
+ `src/components/common/StatusBadge.vue`:
1252
+ ```vue
1253
+ <template>
1254
+ <span :class="badgeClass">
1255
+ <span v-if="showDot" class="w-2 h-2 rounded-full mr-1.5" :class="dotClass"></span>
1256
+ {{ label }}
1257
+ </span>
1258
+ </template>
1259
+
1260
+ <script setup>
1261
+ import { computed } from 'vue';
1262
+
1263
+ const props = defineProps({
1264
+ status: {
1265
+ type: String,
1266
+ required: true,
1267
+ },
1268
+ showDot: {
1269
+ type: Boolean,
1270
+ default: true,
1271
+ },
1272
+ });
1273
+
1274
+ const statusConfig = {
1275
+ healthy: { class: 'badge-success', dot: 'bg-green-500', label: 'Healthy' },
1276
+ stable: { class: 'badge-success', dot: 'bg-green-500', label: 'Stable' },
1277
+ warning: { class: 'badge-warning', dot: 'bg-yellow-500', label: 'Warning' },
1278
+ error: { class: 'badge-danger', dot: 'bg-red-500', label: 'Error' },
1279
+ unhealthy: { class: 'badge-danger', dot: 'bg-red-500', label: 'Unhealthy' },
1280
+ pending: { class: 'badge-warning', dot: 'bg-yellow-500', label: 'Pending' },
1281
+ processing: { class: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', dot: 'bg-blue-500', label: 'Processing' },
1282
+ completed: { class: 'badge-success', dot: 'bg-green-500', label: 'Completed' },
1283
+ failed: { class: 'badge-danger', dot: 'bg-red-500', label: 'Failed' },
1284
+ };
1285
+
1286
+ const config = computed(() => statusConfig[props.status.toLowerCase()] || statusConfig.pending);
1287
+ const badgeClass = computed(() => `badge ${config.value.class}`);
1288
+ const dotClass = computed(() => config.value.dot);
1289
+ const label = computed(() => config.value.label);
1290
+ </script>
1291
+ ```
1292
+
1293
+ `src/components/common/LoadingSpinner.vue`:
1294
+ ```vue
1295
+ <template>
1296
+ <div class="flex items-center justify-center p-8">
1297
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
1298
+ </div>
1299
+ </template>
1300
+ ```
1301
+
1302
+ `src/components/common/RefreshButton.vue`:
1303
+ ```vue
1304
+ <template>
1305
+ <button
1306
+ @click="$emit('refresh')"
1307
+ :disabled="loading"
1308
+ class="btn btn-secondary flex items-center gap-2"
1309
+ >
1310
+ <svg
1311
+ class="w-4 h-4"
1312
+ :class="{ 'animate-spin': loading }"
1313
+ fill="none"
1314
+ stroke="currentColor"
1315
+ viewBox="0 0 24 24"
1316
+ >
1317
+ <path
1318
+ stroke-linecap="round"
1319
+ stroke-linejoin="round"
1320
+ stroke-width="2"
1321
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
1322
+ />
1323
+ </svg>
1324
+ Refresh
1325
+ </button>
1326
+ </template>
1327
+
1328
+ <script setup>
1329
+ defineProps({
1330
+ loading: Boolean,
1331
+ });
1332
+
1333
+ defineEmits(['refresh']);
1334
+ </script>
1335
+ ```
1336
+
1337
+ **3.2 Utility Functions**
1338
+
1339
+ `src/utils/formatters.js`:
1340
+ ```javascript
1341
+ export function formatNumber(num) {
1342
+ if (num >= 1000000) {
1343
+ return (num / 1000000).toFixed(1) + 'M';
1344
+ }
1345
+ if (num >= 1000) {
1346
+ return (num / 1000).toFixed(1) + 'K';
1347
+ }
1348
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
1349
+ }
1350
+
1351
+ export function formatBytes(bytes) {
1352
+ if (bytes === 0) return '0 B';
1353
+ const k = 1024;
1354
+ const sizes = ['B', 'KB', 'MB', 'GB'];
1355
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1356
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1357
+ }
1358
+
1359
+ export function formatDuration(ms) {
1360
+ if (ms < 1000) return `${ms}ms`;
1361
+
1362
+ const seconds = Math.floor(ms / 1000);
1363
+ const minutes = Math.floor(seconds / 60);
1364
+ const hours = Math.floor(minutes / 60);
1365
+ const days = Math.floor(hours / 24);
1366
+
1367
+ if (days > 0) return `${days}d ${hours % 24}h`;
1368
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
1369
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
1370
+ return `${seconds}s`;
1371
+ }
1372
+
1373
+ export function formatDate(date) {
1374
+ return new Date(date).toLocaleString('en-US', {
1375
+ month: 'short',
1376
+ day: 'numeric',
1377
+ hour: '2-digit',
1378
+ minute: '2-digit',
1379
+ });
1380
+ }
1381
+
1382
+ export function formatTime(date) {
1383
+ return new Date(date).toLocaleTimeString('en-US', {
1384
+ hour: '2-digit',
1385
+ minute: '2-digit',
1386
+ second: '2-digit',
1387
+ });
1388
+ }
1389
+ ```
1390
+
1391
+ ---
1392
+
1393
+ ### Phase 4: Dashboard Page (Day 3)
1394
+
1395
+ **4.1 Dashboard View**
1396
+
1397
+ `src/views/Dashboard.vue`:
1398
+ ```vue
1399
+ <template>
1400
+ <div class="space-y-6">
1401
+ <div class="flex items-center justify-between">
1402
+ <h2 class="text-2xl font-bold">Dashboard</h2>
1403
+ <RefreshButton :loading="loading" @refresh="loadData" />
1404
+ </div>
1405
+
1406
+ <LoadingSpinner v-if="loading && !overview" />
1407
+
1408
+ <div v-else-if="error" class="card bg-red-50 dark:bg-red-900/20 text-red-600">
1409
+ <p>Error loading dashboard: {{ error }}</p>
1410
+ </div>
1411
+
1412
+ <template v-else>
1413
+ <!-- Metric Cards -->
1414
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
1415
+ <MetricCard
1416
+ title="Queues"
1417
+ :value="overview?.queues || 0"
1418
+ />
1419
+ <MetricCard
1420
+ title="Partitions"
1421
+ :value="overview?.partitions || 0"
1422
+ />
1423
+ <MetricCard
1424
+ title="Total Messages"
1425
+ :value="overview?.messages?.total || 0"
1426
+ />
1427
+ <MetricCard
1428
+ title="Uptime"
1429
+ :value="health?.uptime || '-'"
1430
+ />
1431
+ </div>
1432
+
1433
+ <!-- Throughput Chart -->
1434
+ <div class="card">
1435
+ <h3 class="text-lg font-semibold mb-4">Message Throughput</h3>
1436
+ <ThroughputChart :data="status" />
1437
+ </div>
1438
+
1439
+ <!-- Stats Row -->
1440
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
1441
+ <MessageStatusCard :data="overview?.messages" />
1442
+ <PerformanceCard :data="metrics" />
1443
+ </div>
1444
+
1445
+ <!-- Top Queues -->
1446
+ <div class="card">
1447
+ <h3 class="text-lg font-semibold mb-4">Top Queues by Activity</h3>
1448
+ <TopQueuesTable :queues="topQueues" />
1449
+ </div>
1450
+ </template>
1451
+ </div>
1452
+ </template>
1453
+
1454
+ <script setup>
1455
+ import { ref, onMounted } from 'vue';
1456
+ import { healthApi } from '../api/health';
1457
+ import { resourcesApi } from '../api/resources';
1458
+ import { analyticsApi } from '../api/analytics';
1459
+ import { queuesApi } from '../api/queues';
1460
+
1461
+ import MetricCard from '../components/common/MetricCard.vue';
1462
+ import LoadingSpinner from '../components/common/LoadingSpinner.vue';
1463
+ import RefreshButton from '../components/common/RefreshButton.vue';
1464
+ import ThroughputChart from '../components/dashboard/ThroughputChart.vue';
1465
+ import MessageStatusCard from '../components/dashboard/MessageStatusCard.vue';
1466
+ import PerformanceCard from '../components/dashboard/PerformanceCard.vue';
1467
+ import TopQueuesTable from '../components/dashboard/TopQueuesTable.vue';
1468
+
1469
+ const loading = ref(false);
1470
+ const error = ref(null);
1471
+ const overview = ref(null);
1472
+ const health = ref(null);
1473
+ const metrics = ref(null);
1474
+ const status = ref(null);
1475
+ const topQueues = ref([]);
1476
+
1477
+ async function loadData() {
1478
+ loading.value = true;
1479
+ error.value = null;
1480
+
1481
+ try {
1482
+ const [overviewRes, healthRes, metricsRes, statusRes, queuesRes] = await Promise.all([
1483
+ resourcesApi.getOverview(),
1484
+ healthApi.getHealth(),
1485
+ healthApi.getMetrics(),
1486
+ analyticsApi.getStatus(),
1487
+ queuesApi.getQueues(),
1488
+ ]);
1489
+
1490
+ overview.value = overviewRes.data;
1491
+ health.value = healthRes.data;
1492
+ metrics.value = metricsRes.data;
1493
+ status.value = statusRes.data;
1494
+
1495
+ // Get top 5 queues by total messages
1496
+ topQueues.value = queuesRes.data.queues
1497
+ .sort((a, b) => (b.messages?.total || 0) - (a.messages?.total || 0))
1498
+ .slice(0, 5);
1499
+ } catch (err) {
1500
+ error.value = err.message;
1501
+ } finally {
1502
+ loading.value = false;
1503
+ }
1504
+ }
1505
+
1506
+ onMounted(() => {
1507
+ loadData();
1508
+ });
1509
+ </script>
1510
+ ```
1511
+
1512
+ **4.2 Dashboard Components**
1513
+
1514
+ `src/components/dashboard/HealthIndicator.vue`:
1515
+ ```vue
1516
+ <template>
1517
+ <div class="flex items-center gap-2">
1518
+ <span
1519
+ class="w-2 h-2 rounded-full"
1520
+ :class="isHealthy ? 'bg-green-500' : 'bg-red-500'"
1521
+ ></span>
1522
+ <span class="text-sm font-medium">{{ statusText }}</span>
1523
+ </div>
1524
+ </template>
1525
+
1526
+ <script setup>
1527
+ import { ref, computed, onMounted } from 'vue';
1528
+ import { healthApi } from '../../api/health';
1529
+
1530
+ const health = ref(null);
1531
+
1532
+ const isHealthy = computed(() => health.value?.status === 'healthy');
1533
+ const statusText = computed(() => isHealthy.value ? 'Healthy' : 'Unhealthy');
1534
+
1535
+ async function checkHealth() {
1536
+ try {
1537
+ const response = await healthApi.getHealth();
1538
+ health.value = response.data;
1539
+ } catch (error) {
1540
+ health.value = { status: 'unhealthy' };
1541
+ }
1542
+ }
1543
+
1544
+ onMounted(() => {
1545
+ checkHealth();
1546
+ // Check health every 30 seconds
1547
+ setInterval(checkHealth, 30000);
1548
+ });
1549
+ </script>
1550
+ ```
1551
+
1552
+ `src/components/dashboard/ThroughputChart.vue`:
1553
+ ```vue
1554
+ <template>
1555
+ <div class="h-64">
1556
+ <Line v-if="chartData" :data="chartData" :options="chartOptions" />
1557
+ <div v-else class="flex items-center justify-center h-full text-gray-500">
1558
+ No data available
1559
+ </div>
1560
+ </div>
1561
+ </template>
1562
+
1563
+ <script setup>
1564
+ import { computed } from 'vue';
1565
+ import { Line } from 'vue-chartjs';
1566
+ import {
1567
+ Chart as ChartJS,
1568
+ CategoryScale,
1569
+ LinearScale,
1570
+ PointElement,
1571
+ LineElement,
1572
+ Title,
1573
+ Tooltip,
1574
+ Legend,
1575
+ Filler,
1576
+ } from 'chart.js';
1577
+
1578
+ ChartJS.register(
1579
+ CategoryScale,
1580
+ LinearScale,
1581
+ PointElement,
1582
+ LineElement,
1583
+ Title,
1584
+ Tooltip,
1585
+ Legend,
1586
+ Filler
1587
+ );
1588
+
1589
+ const props = defineProps({
1590
+ data: Object,
1591
+ });
1592
+
1593
+ const chartData = computed(() => {
1594
+ if (!props.data?.throughput?.length) return null;
1595
+
1596
+ const throughput = props.data.throughput;
1597
+
1598
+ return {
1599
+ labels: throughput.map(t => {
1600
+ const date = new Date(t.timestamp);
1601
+ return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
1602
+ }),
1603
+ datasets: [
1604
+ {
1605
+ label: 'Ingested',
1606
+ data: throughput.map(t => t.ingestedPerSecond),
1607
+ borderColor: 'rgb(59, 130, 246)',
1608
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
1609
+ fill: true,
1610
+ tension: 0.4,
1611
+ },
1612
+ {
1613
+ label: 'Processed',
1614
+ data: throughput.map(t => t.processedPerSecond),
1615
+ borderColor: 'rgb(16, 185, 129)',
1616
+ backgroundColor: 'rgba(16, 185, 129, 0.1)',
1617
+ fill: true,
1618
+ tension: 0.4,
1619
+ },
1620
+ ],
1621
+ };
1622
+ });
1623
+
1624
+ const chartOptions = {
1625
+ responsive: true,
1626
+ maintainAspectRatio: false,
1627
+ interaction: {
1628
+ mode: 'index',
1629
+ intersect: false,
1630
+ },
1631
+ scales: {
1632
+ y: {
1633
+ beginAtZero: true,
1634
+ ticks: {
1635
+ callback: (value) => value + ' msg/s',
1636
+ },
1637
+ },
1638
+ },
1639
+ plugins: {
1640
+ legend: {
1641
+ position: 'top',
1642
+ },
1643
+ tooltip: {
1644
+ callbacks: {
1645
+ label: (context) => {
1646
+ return `${context.dataset.label}: ${context.parsed.y} msg/s`;
1647
+ },
1648
+ },
1649
+ },
1650
+ },
1651
+ };
1652
+ </script>
1653
+ ```
1654
+
1655
+ `src/components/dashboard/MessageStatusCard.vue`:
1656
+ ```vue
1657
+ <template>
1658
+ <div class="card">
1659
+ <h3 class="text-lg font-semibold mb-4">Message Status</h3>
1660
+ <div class="space-y-3">
1661
+ <div class="flex items-center justify-between">
1662
+ <span class="text-gray-600 dark:text-gray-400">Pending</span>
1663
+ <span class="font-semibold">{{ formatNumber(data?.pending || 0) }}</span>
1664
+ </div>
1665
+ <div class="flex items-center justify-between">
1666
+ <span class="text-gray-600 dark:text-gray-400">Processing</span>
1667
+ <span class="font-semibold">{{ formatNumber(data?.processing || 0) }}</span>
1668
+ </div>
1669
+ <div class="flex items-center justify-between">
1670
+ <span class="text-gray-600 dark:text-gray-400">Completed</span>
1671
+ <span class="font-semibold text-green-600">{{ formatNumber(data?.completed || 0) }}</span>
1672
+ </div>
1673
+ <div class="flex items-center justify-between">
1674
+ <span class="text-gray-600 dark:text-gray-400">Failed</span>
1675
+ <span class="font-semibold text-red-600">{{ formatNumber(data?.failed || 0) }}</span>
1676
+ </div>
1677
+ <div class="flex items-center justify-between">
1678
+ <span class="text-gray-600 dark:text-gray-400">Dead Letter</span>
1679
+ <span class="font-semibold text-gray-600">{{ formatNumber(data?.deadLetter || 0) }}</span>
1680
+ </div>
1681
+ </div>
1682
+ </div>
1683
+ </template>
1684
+
1685
+ <script setup>
1686
+ import { formatNumber } from '../../utils/formatters';
1687
+
1688
+ defineProps({
1689
+ data: Object,
1690
+ });
1691
+ </script>
1692
+ ```
1693
+
1694
+ `src/components/dashboard/PerformanceCard.vue`:
1695
+ ```vue
1696
+ <template>
1697
+ <div class="card">
1698
+ <h3 class="text-lg font-semibold mb-4">System Performance</h3>
1699
+ <div class="space-y-3">
1700
+ <div class="flex items-center justify-between">
1701
+ <span class="text-gray-600 dark:text-gray-400">Requests/sec</span>
1702
+ <span class="font-semibold">{{ data?.requests?.rate?.toFixed(2) || '0.00' }}</span>
1703
+ </div>
1704
+ <div class="flex items-center justify-between">
1705
+ <span class="text-gray-600 dark:text-gray-400">Messages/sec</span>
1706
+ <span class="font-semibold">{{ data?.messages?.rate?.toFixed(2) || '0.00' }}</span>
1707
+ </div>
1708
+ <div class="flex items-center justify-between">
1709
+ <span class="text-gray-600 dark:text-gray-400">DB Connections</span>
1710
+ <span class="font-semibold">
1711
+ {{ data?.database?.poolSize - data?.database?.idleConnections || 0 }}/{{ data?.database?.poolSize || 0 }}
1712
+ </span>
1713
+ </div>
1714
+ <div class="flex items-center justify-between">
1715
+ <span class="text-gray-600 dark:text-gray-400">Memory (Heap)</span>
1716
+ <span class="font-semibold">{{ formatBytes(data?.memory?.heapUsed || 0) }}</span>
1717
+ </div>
1718
+ <div class="flex items-center justify-between">
1719
+ <span class="text-gray-600 dark:text-gray-400">CPU Time</span>
1720
+ <span class="font-semibold">{{ formatDuration((data?.cpu?.user + data?.cpu?.system) / 1000 || 0) }}</span>
1721
+ </div>
1722
+ </div>
1723
+ </div>
1724
+ </template>
1725
+
1726
+ <script setup>
1727
+ import { formatBytes, formatDuration } from '../../utils/formatters';
1728
+
1729
+ defineProps({
1730
+ data: Object,
1731
+ });
1732
+ </script>
1733
+ ```
1734
+
1735
+ `src/components/dashboard/TopQueuesTable.vue`:
1736
+ ```vue
1737
+ <template>
1738
+ <div class="overflow-x-auto">
1739
+ <table class="w-full">
1740
+ <thead class="text-left text-sm text-gray-500 border-b">
1741
+ <tr>
1742
+ <th class="pb-3 font-medium">Queue Name</th>
1743
+ <th class="pb-3 font-medium text-right">Pending</th>
1744
+ <th class="pb-3 font-medium text-right">Processing</th>
1745
+ <th class="pb-3 font-medium text-right">Completed</th>
1746
+ </tr>
1747
+ </thead>
1748
+ <tbody class="text-sm">
1749
+ <tr
1750
+ v-for="queue in queues"
1751
+ :key="queue.id"
1752
+ class="border-b last:border-b-0 hover:bg-gray-50 dark:hover:bg-slate-700/50 cursor-pointer"
1753
+ @click="navigateToQueue(queue.name)"
1754
+ >
1755
+ <td class="py-3">
1756
+ <div class="font-medium">{{ queue.name }}</div>
1757
+ <div v-if="queue.namespace" class="text-xs text-gray-500">{{ queue.namespace }}</div>
1758
+ </td>
1759
+ <td class="py-3 text-right">{{ formatNumber(queue.messages?.pending || 0) }}</td>
1760
+ <td class="py-3 text-right">{{ formatNumber(queue.messages?.processing || 0) }}</td>
1761
+ <td class="py-3 text-right">{{ formatNumber(queue.messages?.total || 0) }}</td>
1762
+ </tr>
1763
+ </tbody>
1764
+ </table>
1765
+
1766
+ <div v-if="!queues?.length" class="text-center py-8 text-gray-500">
1767
+ No queues available
1768
+ </div>
1769
+ </div>
1770
+ </template>
1771
+
1772
+ <script setup>
1773
+ import { useRouter } from 'vue-router';
1774
+ import { formatNumber } from '../../utils/formatters';
1775
+
1776
+ defineProps({
1777
+ queues: Array,
1778
+ });
1779
+
1780
+ const router = useRouter();
1781
+
1782
+ function navigateToQueue(queueName) {
1783
+ router.push(`/queues/${queueName}`);
1784
+ }
1785
+ </script>
1786
+ ```
1787
+
1788
+ ---
1789
+
1790
+ ### Phase 5: Remaining Pages (Day 4-5)
1791
+
1792
+ Continue implementing:
1793
+ - Queues page with list and filters
1794
+ - Queue Detail page with full information
1795
+ - Messages page with browsing capabilities
1796
+ - Consumer Groups page
1797
+ - Analytics page with charts
1798
+
1799
+ Each page follows similar patterns established in the Dashboard.
1800
+
1801
+ ---
1802
+
1803
+ ### Phase 6: Testing & Polish (Day 6)
1804
+
1805
+ 1. Test all API integrations
1806
+ 2. Verify dark mode consistency
1807
+ 3. Test responsive layouts
1808
+ 4. Add error boundaries
1809
+ 5. Optimize performance
1810
+ 6. Add loading states everywhere
1811
+ 7. Test with real queue data
1812
+
1813
+ ---
1814
+
1815
+ ## Chart Configuration
1816
+
1817
+ ### Chart.js Setup
1818
+
1819
+ All charts use consistent color schemes:
1820
+
1821
+ **Light Mode:**
1822
+ - Primary: Blue (#3b82f6)
1823
+ - Success: Green (#10b981)
1824
+ - Warning: Yellow (#f59e0b)
1825
+ - Danger: Red (#ef4444)
1826
+
1827
+ **Dark Mode:**
1828
+ - Primary: Light Blue (#60a5fa)
1829
+ - Success: Light Green (#34d399)
1830
+ - Warning: Light Yellow (#fbbf24)
1831
+ - Danger: Light Red (#f87171)
1832
+
1833
+ ### Chart Types Used:
1834
+
1835
+ 1. **Line Charts:** Throughput, time-series data
1836
+ 2. **Bar Charts:** Queue comparisons, volume metrics
1837
+ 3. **Doughnut Charts:** Message status distribution
1838
+ 4. **Stacked Bar Charts:** Multi-metric comparisons
1839
+
1840
+ ---
1841
+
1842
+ ## Environment Configuration
1843
+
1844
+ `.env`:
1845
+ ```
1846
+ VITE_API_BASE_URL=http://localhost:4000
1847
+ VITE_APP_TITLE=Queen Message Queue
1848
+ ```
1849
+
1850
+ ---
1851
+
1852
+ ## Build & Deploy
1853
+
1854
+ `package.json` scripts:
1855
+ ```json
1856
+ {
1857
+ "scripts": {
1858
+ "dev": "vite",
1859
+ "build": "vite build",
1860
+ "preview": "vite preview"
1861
+ }
1862
+ }
1863
+ ```
1864
+
1865
+ Build for production:
1866
+ ```bash
1867
+ npm run build
1868
+ ```
1869
+
1870
+ Output in `dist/` directory ready for deployment.
1871
+
1872
+ ---
1873
+
1874
+ ## Summary
1875
+
1876
+ This design creates a **professional, clean, and highly functional** web application for Queen Message Queue with:
1877
+
1878
+ ✅ **Real API Integration** - Every component uses actual endpoints, no mocked data
1879
+ ✅ **Beautiful UI** - Modern design with Tailwind, proper spacing, typography
1880
+ ✅ **Dark Mode** - Full light/dark theme support
1881
+ ✅ **Responsive** - Works on desktop and tablet
1882
+ ✅ **Clear Navigation** - Intuitive sidebar with logical page organization
1883
+ ✅ **Data Visualization** - Meaningful charts showing system health and activity
1884
+ ✅ **Manual Refresh** - No auto-refresh, user-controlled data updates
1885
+ ✅ **Clean Code** - Vue 3 Composition API, modular structure
1886
+ ✅ **No Animations** - Minimal transitions, focus on content
1887
+
1888
+ The application provides complete visibility into the message queue system with intuitive interactions for managing queues, monitoring consumers, browsing messages, and analyzing system performance.
1889
+