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,418 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="dashboard-flat">
|
|
3
|
+
<div class="py-4 px-3">
|
|
4
|
+
<div class="space-y-2.5">
|
|
5
|
+
<LoadingSpinner v-if="loading && !overview" />
|
|
6
|
+
|
|
7
|
+
<div v-else-if="error" class="error-card">
|
|
8
|
+
<p><strong>Error loading dashboard:</strong> {{ error }}</p>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<template v-else>
|
|
12
|
+
<!-- Metric Cards - Flat -->
|
|
13
|
+
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2">
|
|
14
|
+
<div class="metric-flat">
|
|
15
|
+
<div class="flex items-start gap-3">
|
|
16
|
+
<div class="metric-icon-flat bg-rose-500/10 dark:bg-rose-500/20">
|
|
17
|
+
<svg class="w-5 h-5 text-rose-600 dark:text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
18
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
|
19
|
+
</svg>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="flex-1">
|
|
22
|
+
<p class="metric-label">Queues</p>
|
|
23
|
+
<p class="metric-value-flat-sm">{{ formatNumber(overview?.queues || 0) }}</p>
|
|
24
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
25
|
+
Partitions: <span class="font-semibold">{{ formatNumber(overview?.partitions || 0) }}</span>
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="metric-flat">
|
|
32
|
+
<div class="flex items-start gap-3">
|
|
33
|
+
<div class="metric-icon-flat bg-yellow-500/10 dark:bg-yellow-500/20">
|
|
34
|
+
<svg class="w-5 h-5 text-yellow-600 dark:text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
35
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
36
|
+
</svg>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="flex-1">
|
|
39
|
+
<p class="metric-label">Pending</p>
|
|
40
|
+
<p class="metric-value-flat-sm">{{ formatNumber(calculatedPending) }}</p>
|
|
41
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
42
|
+
Processing: <span class="font-semibold">{{ formatNumber(overview?.messages?.processing || 0) }}</span>
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="metric-flat">
|
|
49
|
+
<div class="flex items-start gap-3">
|
|
50
|
+
<div class="metric-icon-flat bg-green-500/10 dark:bg-green-500/20">
|
|
51
|
+
<svg class="w-5 h-5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
52
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
53
|
+
</svg>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="flex-1">
|
|
56
|
+
<p class="metric-label">Completed</p>
|
|
57
|
+
<p class="metric-value-flat-sm text-green-600 dark:text-green-400">{{ formatNumber(overview?.messages?.completed || 0) }}</p>
|
|
58
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
59
|
+
Total in DB: <span class="font-semibold">{{ formatNumber(overview?.messages?.total || 0) }}</span>
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="metric-flat">
|
|
66
|
+
<div class="flex items-start gap-3">
|
|
67
|
+
<div class="metric-icon-flat bg-red-500/10 dark:bg-red-500/20">
|
|
68
|
+
<svg class="w-5 h-5 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
69
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
70
|
+
</svg>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="flex-1">
|
|
73
|
+
<p class="metric-label">Failed</p>
|
|
74
|
+
<p class="metric-value-flat-sm text-red-600 dark:text-red-400">{{ formatNumber(overview?.messages?.failed || 0) }}</p>
|
|
75
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
76
|
+
Dead Letter: <span class="font-semibold">{{ formatNumber(overview?.messages?.deadLetter || 0) }}</span>
|
|
77
|
+
</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- Throughput Chart - Flat -->
|
|
84
|
+
<div class="chart-flat">
|
|
85
|
+
<div class="flex items-center justify-between mb-4">
|
|
86
|
+
<div class="flex items-center gap-2">
|
|
87
|
+
<div class="w-8 h-8 rounded-lg bg-rose-500/10 dark:bg-rose-500/20 flex items-center justify-center">
|
|
88
|
+
<svg class="w-5 h-5 text-rose-600 dark:text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
89
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" />
|
|
90
|
+
</svg>
|
|
91
|
+
</div>
|
|
92
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Message Throughput</h3>
|
|
93
|
+
</div>
|
|
94
|
+
<span class="text-xs text-gray-500 dark:text-gray-400 px-2.5 py-1 bg-gray-100 dark:bg-slate-700 rounded-full">
|
|
95
|
+
Last Hour
|
|
96
|
+
</span>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="chart-area">
|
|
99
|
+
<ThroughputChart :data="status" />
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- Stats Row - Flat -->
|
|
104
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
|
|
105
|
+
<div class="info-flat">
|
|
106
|
+
<div class="flex items-center gap-2 mb-2">
|
|
107
|
+
<div class="w-7 h-7 rounded-lg bg-purple-500/10 dark:bg-purple-500/20 flex items-center justify-center">
|
|
108
|
+
<svg class="w-4 h-4 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
109
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
110
|
+
</svg>
|
|
111
|
+
</div>
|
|
112
|
+
<h3 class="text-sm font-semibold">Message Status</h3>
|
|
113
|
+
</div>
|
|
114
|
+
<MessageStatusCard :data="overview?.messages" :calculated-pending="calculatedPending" />
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="info-flat">
|
|
118
|
+
<div class="flex items-center gap-2 mb-2">
|
|
119
|
+
<div class="w-7 h-7 rounded-lg bg-blue-500/10 dark:bg-blue-500/20 flex items-center justify-center">
|
|
120
|
+
<svg class="w-4 h-4 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
121
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
122
|
+
</svg>
|
|
123
|
+
</div>
|
|
124
|
+
<h3 class="text-sm font-semibold">System Performance</h3>
|
|
125
|
+
</div>
|
|
126
|
+
<PerformanceCard :data="metrics" />
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Top Queues - Flat -->
|
|
131
|
+
<div class="table-flat">
|
|
132
|
+
<div class="flex items-center gap-2 mb-4">
|
|
133
|
+
<div class="w-7 h-7 rounded-lg bg-indigo-500/10 dark:bg-indigo-500/20 flex items-center justify-center">
|
|
134
|
+
<svg class="w-4 h-4 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
135
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
|
|
136
|
+
</svg>
|
|
137
|
+
</div>
|
|
138
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Top Queues by Activity</h3>
|
|
139
|
+
</div>
|
|
140
|
+
<TopQueuesTable :queues="topQueues" />
|
|
141
|
+
</div>
|
|
142
|
+
</template>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
147
|
+
|
|
148
|
+
<script setup>
|
|
149
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
150
|
+
import { healthApi } from '../api/health';
|
|
151
|
+
import { resourcesApi } from '../api/resources';
|
|
152
|
+
import { analyticsApi } from '../api/analytics';
|
|
153
|
+
import { queuesApi } from '../api/queues';
|
|
154
|
+
import { formatNumber } from '../utils/formatters';
|
|
155
|
+
|
|
156
|
+
import LoadingSpinner from '../components/common/LoadingSpinner.vue';
|
|
157
|
+
import ThroughputChart from '../components/dashboard/ThroughputChart.vue';
|
|
158
|
+
import MessageStatusCard from '../components/dashboard/MessageStatusCard.vue';
|
|
159
|
+
import PerformanceCard from '../components/dashboard/PerformanceCard.vue';
|
|
160
|
+
import TopQueuesTable from '../components/dashboard/TopQueuesTable.vue';
|
|
161
|
+
|
|
162
|
+
const loading = ref(false);
|
|
163
|
+
const error = ref(null);
|
|
164
|
+
const overview = ref(null);
|
|
165
|
+
const health = ref(null);
|
|
166
|
+
const metrics = ref(null);
|
|
167
|
+
const status = ref(null);
|
|
168
|
+
const topQueues = ref([]);
|
|
169
|
+
|
|
170
|
+
// Calculate pending as: total - completed - failed - deadLetter
|
|
171
|
+
const calculatedPending = computed(() => {
|
|
172
|
+
if (!overview.value?.messages) return 0;
|
|
173
|
+
|
|
174
|
+
const total = overview.value.messages.total || 0;
|
|
175
|
+
const completed = overview.value.messages.completed || 0;
|
|
176
|
+
const failed = overview.value.messages.failed || 0;
|
|
177
|
+
const deadLetter = overview.value.messages.deadLetter || 0;
|
|
178
|
+
const processing = overview.value.messages.processing || 0;
|
|
179
|
+
|
|
180
|
+
// Pending = Total - (Completed + Failed + DLQ + Processing)
|
|
181
|
+
const pending = total - completed - failed - deadLetter - processing;
|
|
182
|
+
|
|
183
|
+
return Math.max(0, pending); // Ensure non-negative
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
async function loadData() {
|
|
187
|
+
loading.value = true;
|
|
188
|
+
error.value = null;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const [overviewRes, healthRes, metricsRes, statusRes, queuesRes] = await Promise.all([
|
|
192
|
+
resourcesApi.getOverview(),
|
|
193
|
+
healthApi.getHealth(),
|
|
194
|
+
healthApi.getMetrics(),
|
|
195
|
+
analyticsApi.getStatus(),
|
|
196
|
+
queuesApi.getQueues(),
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
overview.value = overviewRes.data;
|
|
200
|
+
health.value = healthRes.data;
|
|
201
|
+
metrics.value = metricsRes.data;
|
|
202
|
+
status.value = statusRes.data;
|
|
203
|
+
|
|
204
|
+
// Get top 5 queues by total messages
|
|
205
|
+
topQueues.value = queuesRes.data.queues
|
|
206
|
+
.sort((a, b) => (b.messages?.total || 0) - (a.messages?.total || 0))
|
|
207
|
+
.slice(0, 5);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
error.value = err.message;
|
|
210
|
+
console.error('Dashboard error:', err);
|
|
211
|
+
} finally {
|
|
212
|
+
loading.value = false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
onMounted(() => {
|
|
217
|
+
loadData();
|
|
218
|
+
|
|
219
|
+
// Register refresh callback for header button
|
|
220
|
+
if (window.registerRefreshCallback) {
|
|
221
|
+
window.registerRefreshCallback('/', loadData);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
onUnmounted(() => {
|
|
226
|
+
// Clean up callback
|
|
227
|
+
if (window.registerRefreshCallback) {
|
|
228
|
+
window.registerRefreshCallback('/', null);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<style scoped>
|
|
234
|
+
/* Tesla-inspired flat design - no borders, no shadows */
|
|
235
|
+
|
|
236
|
+
.dashboard-flat {
|
|
237
|
+
min-height: 100%;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Flat metric cards - white on gray background */
|
|
241
|
+
.metric-flat {
|
|
242
|
+
background: #ffffff;
|
|
243
|
+
border: none;
|
|
244
|
+
box-shadow: none;
|
|
245
|
+
border-radius: 0.75rem;
|
|
246
|
+
padding: 0.75rem;
|
|
247
|
+
transition: all 0.3s ease;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.dark .metric-flat {
|
|
251
|
+
background: #0a0d14;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.metric-flat:hover {
|
|
255
|
+
background: #fafafa;
|
|
256
|
+
transform: translateY(-2px);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.dark .metric-flat:hover {
|
|
260
|
+
background: #101319;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.metric-icon-flat {
|
|
264
|
+
width: 2rem;
|
|
265
|
+
height: 2rem;
|
|
266
|
+
border-radius: 0.5rem;
|
|
267
|
+
display: flex;
|
|
268
|
+
align-items: center;
|
|
269
|
+
justify-content: center;
|
|
270
|
+
flex-shrink: 0;
|
|
271
|
+
transition: all 0.3s ease;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.metric-flat:hover .metric-icon-flat {
|
|
275
|
+
transform: scale(1.05);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.metric-value-flat {
|
|
279
|
+
font-size: 2rem;
|
|
280
|
+
font-weight: 700;
|
|
281
|
+
line-height: 1.1;
|
|
282
|
+
margin-top: 0.25rem;
|
|
283
|
+
background: linear-gradient(135deg, #f43f5e 0%, #ec4899 50%, #a855f7 100%);
|
|
284
|
+
-webkit-background-clip: text;
|
|
285
|
+
-webkit-text-fill-color: transparent;
|
|
286
|
+
background-clip: text;
|
|
287
|
+
letter-spacing: -0.02em;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.metric-value-flat-sm {
|
|
291
|
+
font-size: 1.5rem;
|
|
292
|
+
font-weight: 700;
|
|
293
|
+
line-height: 1.1;
|
|
294
|
+
margin-top: 0.125rem;
|
|
295
|
+
background: linear-gradient(135deg, #f43f5e 0%, #ec4899 50%, #a855f7 100%);
|
|
296
|
+
-webkit-background-clip: text;
|
|
297
|
+
-webkit-text-fill-color: transparent;
|
|
298
|
+
background-clip: text;
|
|
299
|
+
letter-spacing: -0.02em;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* Flat chart card - white on gray background */
|
|
303
|
+
.chart-flat {
|
|
304
|
+
background: #ffffff;
|
|
305
|
+
border: none;
|
|
306
|
+
box-shadow: none;
|
|
307
|
+
border-radius: 0.75rem;
|
|
308
|
+
padding: 1rem;
|
|
309
|
+
transition: all 0.3s ease;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.dark .chart-flat {
|
|
313
|
+
background: #0a0d14;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.chart-flat:hover {
|
|
317
|
+
background: #fafafa;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.dark .chart-flat:hover {
|
|
321
|
+
background: #101319;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.chart-area {
|
|
325
|
+
border: none;
|
|
326
|
+
box-shadow: none;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Flat info cards - white on gray background */
|
|
330
|
+
.info-flat {
|
|
331
|
+
background: #ffffff;
|
|
332
|
+
border: none;
|
|
333
|
+
box-shadow: none;
|
|
334
|
+
border-radius: 0.75rem;
|
|
335
|
+
padding: 1rem;
|
|
336
|
+
transition: all 0.3s ease;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.dark .info-flat {
|
|
340
|
+
background: #0a0d14;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.info-flat:hover {
|
|
344
|
+
background: #fafafa;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.dark .info-flat:hover {
|
|
348
|
+
background: #101319;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Flat table card - white on gray background */
|
|
352
|
+
.table-flat {
|
|
353
|
+
background: #ffffff;
|
|
354
|
+
border: none;
|
|
355
|
+
box-shadow: none;
|
|
356
|
+
border-radius: 0.75rem;
|
|
357
|
+
padding: 1rem;
|
|
358
|
+
transition: all 0.3s ease;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.dark .table-flat {
|
|
362
|
+
background: #0a0d14;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.table-flat:hover {
|
|
366
|
+
background: #fafafa;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.dark .table-flat:hover {
|
|
370
|
+
background: #101319;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Error card - flat */
|
|
374
|
+
.error-card {
|
|
375
|
+
background: transparent;
|
|
376
|
+
border: none;
|
|
377
|
+
box-shadow: none;
|
|
378
|
+
border-radius: 0.75rem;
|
|
379
|
+
padding: 1rem;
|
|
380
|
+
color: #dc2626;
|
|
381
|
+
font-size: 0.875rem;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.dark .error-card {
|
|
385
|
+
color: #fca5a5;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Override global table styles for flat look */
|
|
389
|
+
.table-flat :deep(.table) {
|
|
390
|
+
border: none;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.table-flat :deep(.table thead) {
|
|
394
|
+
background: none;
|
|
395
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.dark .table-flat :deep(.table thead) {
|
|
399
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.table-flat :deep(.table tbody tr) {
|
|
403
|
+
border-color: rgba(0, 0, 0, 0.03);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.dark .table-flat :deep(.table tbody tr) {
|
|
407
|
+
border-color: rgba(255, 255, 255, 0.03);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.table-flat :deep(.table tbody tr:hover) {
|
|
411
|
+
background: rgba(244, 63, 94, 0.03);
|
|
412
|
+
box-shadow: none;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.dark .table-flat :deep(.table tbody tr:hover) {
|
|
416
|
+
background: rgba(244, 63, 94, 0.05);
|
|
417
|
+
}
|
|
418
|
+
</style>
|