queen-mq 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +862 -752
- package/AUTH.md +2044 -0
- package/LICENSE.md +202 -0
- package/README.md +1705 -1051
- package/WEBAPP.md +1889 -0
- package/assets/dashboard-01.png +0 -0
- package/assets/queen-logo-blue.svg +210 -0
- package/assets/queen-logo-cyan.svg +210 -0
- package/assets/queen-logo-indigo.svg +210 -0
- package/assets/queen-logo-orange.svg +210 -0
- package/assets/queen-logo-pink.svg +210 -0
- package/assets/queen-logo-purple.svg +210 -0
- package/assets/queen-logo-rose.svg +239 -0
- package/assets/queen-logo.svg +263 -0
- package/examples/batch-processing.js +58 -0
- package/examples/test-complete-client.js +260 -0
- package/examples/test-dashboard-api.js +200 -0
- package/examples/test-traceid.js +147 -0
- package/package.json +17 -4
- package/server.log +1 -0
- package/src/benchmark/consumer.js +207 -0
- package/src/benchmark/consumer_multi.js +216 -0
- package/src/benchmark/producer.js +75 -0
- package/src/benchmark/producer_multi.js +115 -0
- package/src/client/client.js +300 -31
- package/src/client/queenClient.js +5 -0
- package/src/cluster-server.js +242 -0
- package/src/config.js +19 -5
- package/src/database/connection.js +42 -16
- package/src/database/poolManager.js +7 -0
- package/src/database/schema-v2.sql +194 -130
- package/src/managers/queueManagerOptimized.js +823 -933
- package/src/managers/systemEventManager.js +8 -3
- package/src/routes/messages.js +127 -57
- package/src/routes/pop.js +27 -43
- package/src/routes/resources.js +61 -27
- package/src/routes/status.js +1037 -0
- package/src/server.js +308 -272
- package/src/services/evictionService.js +57 -28
- package/src/services/retentionService.js +44 -11
- package/src/test/MIGRATION_ISSUES.md +174 -0
- package/src/test/README.md +203 -0
- package/src/test/advanced-pattern-tests.js +1137 -0
- package/src/test/bus-mode-tests.js +361 -0
- package/src/test/core-tests.js +342 -0
- package/src/test/edge-case-tests.js +561 -0
- package/src/test/enterprise-tests.js +637 -0
- package/src/test/partition-locking-tests.js +545 -0
- package/src/test/test-new.js +278 -0
- package/src/test/test.js +6 -3
- package/src/test/utils.js +169 -0
- package/src/utils/streaming.js +231 -0
- package/src/utils/uuid.js +2 -2
- package/src/websocket/wsServer.js +10 -3
- package/test-keepalive-v2.sh +22 -0
- package/webapp/COLOR_GUIDE.md +118 -0
- package/webapp/README.md +143 -0
- package/webapp/index.html +14 -0
- package/webapp/package-lock.json +3184 -0
- package/webapp/package.json +25 -0
- package/webapp/postcss.config.js +7 -0
- package/webapp/public/assets/queen-logo-blue.svg +210 -0
- package/webapp/public/assets/queen-logo-cyan.svg +210 -0
- package/webapp/public/assets/queen-logo-indigo.svg +210 -0
- package/webapp/public/assets/queen-logo-orange.svg +210 -0
- package/webapp/public/assets/queen-logo-pink.svg +210 -0
- package/webapp/public/assets/queen-logo-purple.svg +210 -0
- package/webapp/public/assets/queen-logo-rose.svg +239 -0
- package/webapp/public/assets/queen-logo.svg +263 -0
- package/webapp/src/App.vue +19 -0
- package/webapp/src/api/analytics.js +10 -0
- package/webapp/src/api/client.js +29 -0
- package/webapp/src/api/consumers.js +52 -0
- package/webapp/src/api/health.js +7 -0
- package/webapp/src/api/messages.js +26 -0
- package/webapp/src/api/queues.js +14 -0
- package/webapp/src/api/resources.js +8 -0
- package/webapp/src/assets/styles/main.css +357 -0
- package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
- package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
- package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
- package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
- package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
- package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
- package/webapp/src/components/common/ConfirmDialog.vue +56 -0
- package/webapp/src/components/common/LoadingSpinner.vue +6 -0
- package/webapp/src/components/common/MetricCard.vue +43 -0
- package/webapp/src/components/common/StatusBadge.vue +45 -0
- package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
- package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
- package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
- package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
- package/webapp/src/components/layout/AppLayout.vue +110 -0
- package/webapp/src/components/layout/AppSidebar.vue +304 -0
- package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
- package/webapp/src/components/messages/MessageFilters.vue +114 -0
- package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
- package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
- package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
- package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
- package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
- package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
- package/webapp/src/components/queues/QueueFilters.vue +90 -0
- package/webapp/src/composables/useApi.js +34 -0
- package/webapp/src/composables/useTheme.js +36 -0
- package/webapp/src/main.js +11 -0
- package/webapp/src/router/index.js +42 -0
- package/webapp/src/utils/colors.js +96 -0
- package/webapp/src/utils/formatters.js +49 -0
- package/webapp/src/views/Analytics.vue +377 -0
- package/webapp/src/views/ConsumerGroups.vue +433 -0
- package/webapp/src/views/Dashboard.vue +418 -0
- package/webapp/src/views/Messages.vue +361 -0
- package/webapp/src/views/QueueDetail.vue +582 -0
- package/webapp/src/views/Queues.vue +496 -0
- package/webapp/tailwind.config.js +25 -0
- package/webapp/vite.config.js +10 -0
- package/CACHE.md +0 -519
- package/DASHBOARD-V3.md +0 -478
- package/DASHBOARD.md +0 -382
- package/MOD_QUEUE.md +0 -453
- package/PARTITION_LOCKING_DESIGN.md +0 -989
- package/PLAN.md +0 -707
- package/QUERY_ANALSYS.md +0 -72
- package/QUEUE_BUS.md +0 -334
- package/V2-PLAN.md +0 -236
- package/dashboard/.vscode/extensions.json +0 -3
- package/dashboard/README.md +0 -5
- package/dashboard/index.html +0 -14
- package/dashboard/package-lock.json +0 -1458
- package/dashboard/package.json +0 -25
- package/dashboard/public/vite.svg +0 -1
- package/dashboard/src/App.vue +0 -29
- package/dashboard/src/assets/styles/main.css +0 -908
- package/dashboard/src/assets/vue.svg +0 -1
- package/dashboard/src/components/cards/MetricCard.vue +0 -298
- package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
- package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
- package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
- package/dashboard/src/components/common/ActivityFeed.vue +0 -251
- package/dashboard/src/components/layout/AppHeader.vue +0 -208
- package/dashboard/src/components/layout/AppLayout.vue +0 -88
- package/dashboard/src/components/layout/AppSidebar.vue +0 -261
- package/dashboard/src/main.js +0 -44
- package/dashboard/src/router.js +0 -54
- package/dashboard/src/services/api.js +0 -187
- package/dashboard/src/services/websocket.js +0 -167
- package/dashboard/src/utils/constants.js +0 -56
- package/dashboard/src/utils/helpers.js +0 -118
- package/dashboard/src/views/Analytics.vue +0 -912
- package/dashboard/src/views/Dashboard.vue +0 -906
- package/dashboard/src/views/Messages.vue +0 -437
- package/dashboard/src/views/QueueDetail.vue +0 -501
- package/dashboard/src/views/Queues.vue +0 -333
- package/dashboard/vite.config.js +0 -30
- package/debug-namespace.js +0 -110
- package/docs/long-polling.md +0 -159
- package/docs/multi-server-cache-solutions.md +0 -185
- package/docs/performance-tuning.md +0 -222
- package/src/routes/analytics.js +0 -812
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="chart-container">
|
|
3
|
+
<Bar v-if="chartData" :data="chartData" :options="chartOptions" />
|
|
4
|
+
<div v-else class="flex items-center justify-center h-full text-gray-500 text-sm">
|
|
5
|
+
No data available for this time range
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
import { computed } from 'vue';
|
|
12
|
+
import { Bar } from 'vue-chartjs';
|
|
13
|
+
import {
|
|
14
|
+
Chart as ChartJS,
|
|
15
|
+
CategoryScale,
|
|
16
|
+
LinearScale,
|
|
17
|
+
BarElement,
|
|
18
|
+
Title,
|
|
19
|
+
Tooltip,
|
|
20
|
+
Legend,
|
|
21
|
+
} from 'chart.js';
|
|
22
|
+
import { colors } from '../../utils/colors';
|
|
23
|
+
|
|
24
|
+
ChartJS.register(
|
|
25
|
+
CategoryScale,
|
|
26
|
+
LinearScale,
|
|
27
|
+
BarElement,
|
|
28
|
+
Title,
|
|
29
|
+
Tooltip,
|
|
30
|
+
Legend
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
data: Object,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const chartData = computed(() => {
|
|
38
|
+
if (!props.data?.throughput?.length) return null;
|
|
39
|
+
|
|
40
|
+
const throughput = [...props.data.throughput].reverse();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
labels: throughput.map(t => {
|
|
44
|
+
const date = new Date(t.timestamp);
|
|
45
|
+
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
46
|
+
}),
|
|
47
|
+
datasets: [
|
|
48
|
+
{
|
|
49
|
+
label: 'Ingested',
|
|
50
|
+
data: throughput.map(t => t.ingested || t.ingestedPerSecond || 0),
|
|
51
|
+
backgroundColor: createGradientBars('rose'),
|
|
52
|
+
borderColor: 'transparent',
|
|
53
|
+
borderWidth: 0,
|
|
54
|
+
borderRadius: 4,
|
|
55
|
+
barPercentage: 0.9,
|
|
56
|
+
categoryPercentage: 0.95,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: 'Processed',
|
|
60
|
+
data: throughput.map(t => t.processed || t.processedPerSecond || 0),
|
|
61
|
+
backgroundColor: createGradientBars('purple'),
|
|
62
|
+
borderColor: 'transparent',
|
|
63
|
+
borderWidth: 0,
|
|
64
|
+
borderRadius: 4,
|
|
65
|
+
barPercentage: 0.9,
|
|
66
|
+
categoryPercentage: 0.95,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function createGradientBars(color) {
|
|
73
|
+
return (context) => {
|
|
74
|
+
const chart = context.chart;
|
|
75
|
+
const {ctx, chartArea} = chart;
|
|
76
|
+
|
|
77
|
+
if (!chartArea) {
|
|
78
|
+
return color === 'rose' ? colors.charts.ingested.border : colors.charts.processed.border;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
|
|
82
|
+
|
|
83
|
+
if (color === 'rose') {
|
|
84
|
+
gradient.addColorStop(0, 'rgba(244, 63, 94, 0.9)');
|
|
85
|
+
gradient.addColorStop(1, 'rgba(244, 63, 94, 0.5)');
|
|
86
|
+
} else if (color === 'purple') {
|
|
87
|
+
gradient.addColorStop(0, 'rgba(168, 85, 247, 0.9)');
|
|
88
|
+
gradient.addColorStop(1, 'rgba(168, 85, 247, 0.5)');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return gradient;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const chartOptions = {
|
|
96
|
+
responsive: true,
|
|
97
|
+
maintainAspectRatio: false,
|
|
98
|
+
interaction: {
|
|
99
|
+
mode: 'index',
|
|
100
|
+
intersect: false,
|
|
101
|
+
},
|
|
102
|
+
scales: {
|
|
103
|
+
x: {
|
|
104
|
+
stacked: false,
|
|
105
|
+
grid: {
|
|
106
|
+
display: false,
|
|
107
|
+
},
|
|
108
|
+
border: {
|
|
109
|
+
display: false,
|
|
110
|
+
},
|
|
111
|
+
ticks: {
|
|
112
|
+
maxRotation: 0,
|
|
113
|
+
autoSkipPadding: 20,
|
|
114
|
+
font: {
|
|
115
|
+
size: 10,
|
|
116
|
+
},
|
|
117
|
+
color: '#9ca3af',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
y: {
|
|
121
|
+
stacked: false,
|
|
122
|
+
beginAtZero: true,
|
|
123
|
+
border: {
|
|
124
|
+
display: false,
|
|
125
|
+
},
|
|
126
|
+
grid: {
|
|
127
|
+
display: false,
|
|
128
|
+
},
|
|
129
|
+
ticks: {
|
|
130
|
+
font: {
|
|
131
|
+
size: 11,
|
|
132
|
+
},
|
|
133
|
+
color: '#9ca3af',
|
|
134
|
+
padding: 8,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
plugins: {
|
|
139
|
+
legend: {
|
|
140
|
+
display: true,
|
|
141
|
+
position: 'top',
|
|
142
|
+
align: 'end',
|
|
143
|
+
labels: {
|
|
144
|
+
boxWidth: 12,
|
|
145
|
+
boxHeight: 12,
|
|
146
|
+
padding: 15,
|
|
147
|
+
usePointStyle: true,
|
|
148
|
+
pointStyle: 'circle',
|
|
149
|
+
font: {
|
|
150
|
+
size: 12,
|
|
151
|
+
weight: '500',
|
|
152
|
+
},
|
|
153
|
+
color: '#6b7280',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
tooltip: {
|
|
157
|
+
enabled: true,
|
|
158
|
+
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|
159
|
+
padding: 12,
|
|
160
|
+
cornerRadius: 8,
|
|
161
|
+
titleFont: {
|
|
162
|
+
size: 13,
|
|
163
|
+
weight: '600',
|
|
164
|
+
},
|
|
165
|
+
bodyFont: {
|
|
166
|
+
size: 12,
|
|
167
|
+
},
|
|
168
|
+
bodySpacing: 6,
|
|
169
|
+
usePointStyle: true,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center gap-3">
|
|
3
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Time Range:</label>
|
|
4
|
+
<select
|
|
5
|
+
:value="modelValue"
|
|
6
|
+
@change="$emit('update:modelValue', $event.target.value)"
|
|
7
|
+
class="input w-40"
|
|
8
|
+
>
|
|
9
|
+
<option value="1h">Last Hour</option>
|
|
10
|
+
<option value="6h">Last 6 Hours</option>
|
|
11
|
+
<option value="24h">Last 24 Hours</option>
|
|
12
|
+
<option value="7d">Last 7 Days</option>
|
|
13
|
+
</select>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
defineProps({
|
|
19
|
+
modelValue: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: '1h',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
defineEmits(['update:modelValue']);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-80">
|
|
3
|
+
<Bar v-if="chartData" :data="chartData" :options="chartOptions" />
|
|
4
|
+
<div v-else class="flex items-center justify-center h-full text-gray-500 text-sm">
|
|
5
|
+
No queue data available
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
import { computed } from 'vue';
|
|
12
|
+
import { Bar } from 'vue-chartjs';
|
|
13
|
+
import {
|
|
14
|
+
Chart as ChartJS,
|
|
15
|
+
CategoryScale,
|
|
16
|
+
LinearScale,
|
|
17
|
+
BarElement,
|
|
18
|
+
Title,
|
|
19
|
+
Tooltip,
|
|
20
|
+
Legend,
|
|
21
|
+
} from 'chart.js';
|
|
22
|
+
import { colors } from '../../utils/colors';
|
|
23
|
+
|
|
24
|
+
ChartJS.register(
|
|
25
|
+
CategoryScale,
|
|
26
|
+
LinearScale,
|
|
27
|
+
BarElement,
|
|
28
|
+
Title,
|
|
29
|
+
Tooltip,
|
|
30
|
+
Legend
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
queues: Array,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const chartData = computed(() => {
|
|
38
|
+
if (!props.queues?.length) return null;
|
|
39
|
+
|
|
40
|
+
// Take top 10 queues by total messages
|
|
41
|
+
const topQueues = [...props.queues]
|
|
42
|
+
.sort((a, b) => (b.messages?.total || 0) - (a.messages?.total || 0))
|
|
43
|
+
.slice(0, 10);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
labels: topQueues.map(q => q.name),
|
|
47
|
+
datasets: [
|
|
48
|
+
{
|
|
49
|
+
label: 'Total Messages',
|
|
50
|
+
data: topQueues.map(q => q.messages?.total || 0),
|
|
51
|
+
backgroundColor: createGradientBars(),
|
|
52
|
+
borderColor: 'transparent',
|
|
53
|
+
borderWidth: 0,
|
|
54
|
+
borderRadius: 8,
|
|
55
|
+
barThickness: 24,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Create gradient for bars
|
|
62
|
+
function createGradientBars() {
|
|
63
|
+
return (context) => {
|
|
64
|
+
const chart = context.chart;
|
|
65
|
+
const {ctx, chartArea} = chart;
|
|
66
|
+
|
|
67
|
+
if (!chartArea) {
|
|
68
|
+
return colors.secondary.rgba(0.8);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const gradient = ctx.createLinearGradient(chartArea.left, 0, chartArea.right, 0);
|
|
72
|
+
gradient.addColorStop(0, 'rgba(244, 63, 94, 0.8)');
|
|
73
|
+
gradient.addColorStop(0.5, 'rgba(236, 72, 153, 0.8)');
|
|
74
|
+
gradient.addColorStop(1, 'rgba(168, 85, 247, 0.8)');
|
|
75
|
+
|
|
76
|
+
return gradient;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const chartOptions = {
|
|
81
|
+
responsive: true,
|
|
82
|
+
maintainAspectRatio: false,
|
|
83
|
+
indexAxis: 'y',
|
|
84
|
+
scales: {
|
|
85
|
+
x: {
|
|
86
|
+
beginAtZero: true,
|
|
87
|
+
grid: {
|
|
88
|
+
display: false,
|
|
89
|
+
},
|
|
90
|
+
border: {
|
|
91
|
+
display: false,
|
|
92
|
+
},
|
|
93
|
+
ticks: {
|
|
94
|
+
font: {
|
|
95
|
+
size: 11,
|
|
96
|
+
},
|
|
97
|
+
color: '#9ca3af',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
y: {
|
|
101
|
+
grid: {
|
|
102
|
+
display: false,
|
|
103
|
+
},
|
|
104
|
+
border: {
|
|
105
|
+
display: false,
|
|
106
|
+
},
|
|
107
|
+
ticks: {
|
|
108
|
+
font: {
|
|
109
|
+
size: 11,
|
|
110
|
+
},
|
|
111
|
+
color: '#6b7280',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
plugins: {
|
|
116
|
+
legend: {
|
|
117
|
+
display: false,
|
|
118
|
+
},
|
|
119
|
+
tooltip: {
|
|
120
|
+
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|
121
|
+
padding: 12,
|
|
122
|
+
cornerRadius: 8,
|
|
123
|
+
titleFont: {
|
|
124
|
+
size: 13,
|
|
125
|
+
},
|
|
126
|
+
bodyFont: {
|
|
127
|
+
size: 12,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="isOpen" class="fixed inset-0 z-50 overflow-y-auto">
|
|
3
|
+
<div class="flex items-center justify-center min-h-screen px-4">
|
|
4
|
+
<!-- Backdrop with blur -->
|
|
5
|
+
<div
|
|
6
|
+
class="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm transition-opacity animate-fade-in"
|
|
7
|
+
@click="$emit('cancel')"
|
|
8
|
+
></div>
|
|
9
|
+
|
|
10
|
+
<!-- Modal -->
|
|
11
|
+
<div class="relative bg-white dark:bg-slate-800 rounded-xl shadow-2xl max-w-md w-full p-6 animate-scale-in border border-gray-200 dark:border-gray-700">
|
|
12
|
+
<div class="mb-4">
|
|
13
|
+
<h3 class="text-lg font-bold mb-2">{{ title }}</h3>
|
|
14
|
+
<p class="text-sm text-gray-600 dark:text-gray-400">{{ message }}</p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="flex gap-3">
|
|
18
|
+
<button @click="$emit('cancel')" class="btn btn-secondary flex-1">
|
|
19
|
+
{{ cancelText }}
|
|
20
|
+
</button>
|
|
21
|
+
<button @click="$emit('confirm')" :class="`btn ${confirmClass} flex-1`">
|
|
22
|
+
{{ confirmText }}
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
defineProps({
|
|
32
|
+
isOpen: Boolean,
|
|
33
|
+
title: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: 'Confirm Action',
|
|
36
|
+
},
|
|
37
|
+
message: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'Are you sure you want to proceed?',
|
|
40
|
+
},
|
|
41
|
+
confirmText: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: 'Confirm',
|
|
44
|
+
},
|
|
45
|
+
cancelText: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: 'Cancel',
|
|
48
|
+
},
|
|
49
|
+
confirmClass: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: 'btn-primary',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
defineEmits(['confirm', 'cancel']);
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="metric-card animate-fade-in">
|
|
3
|
+
<p class="metric-label">{{ title }}</p>
|
|
4
|
+
<p class="metric-value">{{ formattedValue }}</p>
|
|
5
|
+
<p v-if="subtitle || (secondaryValue !== undefined && secondaryValue !== null)" class="text-xs text-gray-500 dark:text-gray-400 mt-1.5">
|
|
6
|
+
<span v-if="subtitle">{{ subtitle }}</span>
|
|
7
|
+
<span v-else>
|
|
8
|
+
{{ secondaryLabel }}: <span class="font-semibold">{{ formattedSecondary }}</span>
|
|
9
|
+
</span>
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup>
|
|
15
|
+
import { computed } from 'vue';
|
|
16
|
+
import { formatNumber } from '../../utils/formatters';
|
|
17
|
+
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
title: String,
|
|
20
|
+
value: [String, Number],
|
|
21
|
+
subtitle: String,
|
|
22
|
+
secondaryValue: [String, Number],
|
|
23
|
+
secondaryLabel: String,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const formattedValue = computed(() => {
|
|
27
|
+
if (typeof props.value === 'number') {
|
|
28
|
+
return formatNumber(props.value);
|
|
29
|
+
}
|
|
30
|
+
return props.value;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const formattedSecondary = computed(() => {
|
|
34
|
+
if (props.secondaryValue !== undefined && props.secondaryValue !== null) {
|
|
35
|
+
if (typeof props.secondaryValue === 'number') {
|
|
36
|
+
return formatNumber(props.secondaryValue);
|
|
37
|
+
}
|
|
38
|
+
return props.secondaryValue;
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :class="badgeClass" class="flex items-center badge-glow">
|
|
3
|
+
<span v-if="showDot" class="w-2 h-2 rounded-full mr-1.5" :class="dotClass"></span>
|
|
4
|
+
{{ label }}
|
|
5
|
+
</span>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup>
|
|
9
|
+
import { computed } from 'vue';
|
|
10
|
+
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
status: {
|
|
13
|
+
type: String,
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
showDot: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: true,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const statusConfig = {
|
|
23
|
+
healthy: { class: 'badge-success', dot: 'bg-green-500', label: 'Healthy' },
|
|
24
|
+
stable: { class: 'badge-success', dot: 'bg-green-500', label: 'Stable' },
|
|
25
|
+
warning: { class: 'badge-warning', dot: 'bg-yellow-500', label: 'Warning' },
|
|
26
|
+
error: { class: 'badge-danger', dot: 'bg-red-500', label: 'Error' },
|
|
27
|
+
unhealthy: { class: 'badge-danger', dot: 'bg-red-500', label: 'Unhealthy' },
|
|
28
|
+
pending: { class: 'badge-warning', dot: 'bg-yellow-500', label: 'Pending' },
|
|
29
|
+
processing: { class: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400', dot: 'bg-purple-500 animate-pulse', label: 'Processing' },
|
|
30
|
+
completed: { class: 'badge-success', dot: 'bg-green-500', label: 'Completed' },
|
|
31
|
+
failed: { class: 'badge-danger', dot: 'bg-red-500', label: 'Failed' },
|
|
32
|
+
dead_letter: { class: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', dot: 'bg-gray-500', label: 'Dead Letter' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const config = computed(() => statusConfig[props.status.toLowerCase()] || statusConfig.pending);
|
|
36
|
+
const badgeClass = computed(() => `badge ${config.value.class}`);
|
|
37
|
+
const dotClass = computed(() => config.value.dot);
|
|
38
|
+
const label = computed(() => config.value.label);
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<style scoped>
|
|
42
|
+
.badge-glow {
|
|
43
|
+
transition: all 0.2s ease;
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="space-y-2.5">
|
|
4
|
+
<div class="flex items-center justify-between text-sm">
|
|
5
|
+
<span class="text-gray-600 dark:text-gray-400">Pending</span>
|
|
6
|
+
<span class="font-semibold">{{ formatNumber(calculatedPending) }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex items-center justify-between text-sm">
|
|
9
|
+
<span class="text-gray-600 dark:text-gray-400">Processing</span>
|
|
10
|
+
<span class="font-semibold">{{ formatNumber(data?.processing || 0) }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex items-center justify-between text-sm">
|
|
13
|
+
<span class="text-gray-600 dark:text-gray-400">Completed</span>
|
|
14
|
+
<span class="font-semibold text-green-600 dark:text-green-400">
|
|
15
|
+
{{ formatNumber(data?.completed || 0) }}
|
|
16
|
+
</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="flex items-center justify-between text-sm">
|
|
19
|
+
<span class="text-gray-600 dark:text-gray-400">Failed</span>
|
|
20
|
+
<span class="font-semibold text-red-600 dark:text-red-400">
|
|
21
|
+
{{ formatNumber(data?.failed || 0) }}
|
|
22
|
+
</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="flex items-center justify-between text-sm">
|
|
25
|
+
<span class="text-gray-600 dark:text-gray-400">Dead Letter</span>
|
|
26
|
+
<span class="font-semibold text-gray-600 dark:text-gray-400">
|
|
27
|
+
{{ formatNumber(data?.deadLetter || 0) }}
|
|
28
|
+
</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="flex items-center justify-between text-sm pt-2 border-t border-gray-200 dark:border-gray-700">
|
|
31
|
+
<span class="text-gray-600 dark:text-gray-400">Total in DB</span>
|
|
32
|
+
<span class="font-semibold text-gray-500 dark:text-gray-400">
|
|
33
|
+
{{ formatNumber(data?.total || 0) }}
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup>
|
|
41
|
+
import { formatNumber } from '../../utils/formatters';
|
|
42
|
+
|
|
43
|
+
defineProps({
|
|
44
|
+
data: Object,
|
|
45
|
+
calculatedPending: {
|
|
46
|
+
type: Number,
|
|
47
|
+
default: 0,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="space-y-2.5">
|
|
4
|
+
<div class="flex items-center justify-between text-sm">
|
|
5
|
+
<span class="text-gray-600 dark:text-gray-400">Requests/sec</span>
|
|
6
|
+
<span class="font-semibold font-mono">{{ data?.requests?.rate?.toFixed(2) || '0.00' }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex items-center justify-between text-sm">
|
|
9
|
+
<span class="text-gray-600 dark:text-gray-400">Messages/sec</span>
|
|
10
|
+
<span class="font-semibold font-mono">{{ data?.messages?.rate?.toFixed(2) || '0.00' }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex items-center justify-between text-sm">
|
|
13
|
+
<span class="text-gray-600 dark:text-gray-400">DB Connections</span>
|
|
14
|
+
<span class="font-semibold font-mono">
|
|
15
|
+
{{ (data?.database?.poolSize || 0) - (data?.database?.idleConnections || 0) }}/{{ data?.database?.poolSize || 0 }}
|
|
16
|
+
</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="flex items-center justify-between text-sm">
|
|
19
|
+
<span class="text-gray-600 dark:text-gray-400">Memory (Heap)</span>
|
|
20
|
+
<span class="font-semibold font-mono">{{ formatBytes(data?.memory?.heapUsed || 0) }}</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="flex items-center justify-between text-sm">
|
|
23
|
+
<span class="text-gray-600 dark:text-gray-400">CPU Time</span>
|
|
24
|
+
<span class="font-semibold font-mono">
|
|
25
|
+
{{ formatDuration(((data?.cpu?.user || 0) + (data?.cpu?.system || 0)) / 1000) }}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup>
|
|
33
|
+
import { formatBytes, formatDuration } from '../../utils/formatters';
|
|
34
|
+
|
|
35
|
+
defineProps({
|
|
36
|
+
data: Object,
|
|
37
|
+
});
|
|
38
|
+
</script>
|