queen-mq 0.1.1 → 0.1.3

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 (149) hide show
  1. package/API.md +1226 -0
  2. package/AUTH.md +2044 -0
  3. package/LICENSE.md +202 -0
  4. package/PROGRAMMATIC_SERVER.md +190 -0
  5. package/README.md +303 -53
  6. package/WEBAPP.md +1889 -0
  7. package/assets/dashboard-01.png +0 -0
  8. package/assets/queen-logo-blue.svg +210 -0
  9. package/assets/queen-logo-cyan.svg +210 -0
  10. package/assets/queen-logo-indigo.svg +210 -0
  11. package/assets/queen-logo-orange.svg +210 -0
  12. package/assets/queen-logo-pink.svg +210 -0
  13. package/assets/queen-logo-purple.svg +210 -0
  14. package/assets/queen-logo-rose.svg +239 -0
  15. package/assets/queen-logo.svg +263 -0
  16. package/examples/batch-processing.js +58 -0
  17. package/examples/programmatic-server.js +58 -0
  18. package/examples/test-cache-multi-server.js +2 -2
  19. package/examples/test-connection-recovery.js +66 -0
  20. package/examples/test-dashboard-api.js +200 -0
  21. package/package.json +4 -2
  22. package/server.log +1 -0
  23. package/src/benchmark/consumer.js +207 -0
  24. package/src/benchmark/consumer_multi.js +216 -0
  25. package/src/benchmark/producer.js +75 -0
  26. package/src/benchmark/producer_multi.js +115 -0
  27. package/src/client/client.js +219 -17
  28. package/src/client/index.js +4 -3
  29. package/src/cluster-server.js +242 -0
  30. package/src/config.js +19 -5
  31. package/src/database/connection.js +42 -16
  32. package/src/database/poolManager.js +7 -0
  33. package/src/database/schema-v2.sql +194 -130
  34. package/src/managers/queueManagerOptimized.js +852 -933
  35. package/src/managers/systemEventManager.js +8 -3
  36. package/src/routes/messages.js +127 -57
  37. package/src/routes/pop.js +27 -43
  38. package/src/routes/resources.js +61 -27
  39. package/src/routes/status.js +1037 -0
  40. package/src/server.js +704 -642
  41. package/src/services/evictionService.js +57 -28
  42. package/src/services/retentionService.js +44 -11
  43. package/src/services/startupSync.js +1 -1
  44. package/src/test/advanced-pattern-tests.js +6 -6
  45. package/src/test/bus-mode-tests.js +24 -11
  46. package/src/test/core-tests.js +110 -0
  47. package/src/test/edge-case-tests.js +12 -5
  48. package/src/test/enterprise-tests.js +48 -15
  49. package/src/test/test-new.js +3 -1
  50. package/src/test/test.js +1 -1
  51. package/src/test/utils.js +1 -1
  52. package/src/test/window-buffer-test.js +114 -0
  53. package/src/utils/streaming.js +231 -0
  54. package/src/utils/uuid.js +2 -2
  55. package/src/websocket/wsServer.js +10 -3
  56. package/test-keepalive-v2.sh +22 -0
  57. package/webapp/COLOR_GUIDE.md +118 -0
  58. package/webapp/README.md +143 -0
  59. package/webapp/index.html +14 -0
  60. package/webapp/package-lock.json +3184 -0
  61. package/webapp/package.json +25 -0
  62. package/webapp/postcss.config.js +7 -0
  63. package/webapp/public/assets/queen-logo-blue.svg +210 -0
  64. package/webapp/public/assets/queen-logo-cyan.svg +210 -0
  65. package/webapp/public/assets/queen-logo-indigo.svg +210 -0
  66. package/webapp/public/assets/queen-logo-orange.svg +210 -0
  67. package/webapp/public/assets/queen-logo-pink.svg +210 -0
  68. package/webapp/public/assets/queen-logo-purple.svg +210 -0
  69. package/webapp/public/assets/queen-logo-rose.svg +239 -0
  70. package/webapp/public/assets/queen-logo.svg +263 -0
  71. package/webapp/src/App.vue +19 -0
  72. package/webapp/src/api/analytics.js +10 -0
  73. package/webapp/src/api/client.js +29 -0
  74. package/webapp/src/api/consumers.js +52 -0
  75. package/webapp/src/api/health.js +7 -0
  76. package/webapp/src/api/messages.js +26 -0
  77. package/webapp/src/api/queues.js +14 -0
  78. package/webapp/src/api/resources.js +8 -0
  79. package/webapp/src/assets/styles/main.css +357 -0
  80. package/webapp/src/components/analytics/AnalyticsFilters.vue +87 -0
  81. package/webapp/src/components/analytics/AnalyticsMetrics.vue +57 -0
  82. package/webapp/src/components/analytics/MessageDistributionChart.vue +111 -0
  83. package/webapp/src/components/analytics/MessageFlowChart.vue +173 -0
  84. package/webapp/src/components/analytics/TimeRangeSelector.vue +27 -0
  85. package/webapp/src/components/analytics/TopQueuesChart.vue +132 -0
  86. package/webapp/src/components/common/ConfirmDialog.vue +56 -0
  87. package/webapp/src/components/common/LoadingSpinner.vue +6 -0
  88. package/webapp/src/components/common/MetricCard.vue +43 -0
  89. package/webapp/src/components/common/StatusBadge.vue +45 -0
  90. package/webapp/src/components/dashboard/MessageStatusCard.vue +50 -0
  91. package/webapp/src/components/dashboard/PerformanceCard.vue +38 -0
  92. package/webapp/src/components/dashboard/ThroughputChart.vue +182 -0
  93. package/webapp/src/components/dashboard/TopQueuesTable.vue +53 -0
  94. package/webapp/src/components/layout/AppLayout.vue +110 -0
  95. package/webapp/src/components/layout/AppSidebar.vue +304 -0
  96. package/webapp/src/components/messages/MessageDetailPanel.vue +242 -0
  97. package/webapp/src/components/messages/MessageFilters.vue +101 -0
  98. package/webapp/src/components/queue-detail/PartitionList.vue +79 -0
  99. package/webapp/src/components/queue-detail/PushMessageModal.vue +175 -0
  100. package/webapp/src/components/queue-detail/QueueConfig.vue +63 -0
  101. package/webapp/src/components/queue-detail/QueueDetailHeader.vue +53 -0
  102. package/webapp/src/components/queue-detail/RecentMessages.vue +76 -0
  103. package/webapp/src/components/queues/CreateQueueModal.vue +193 -0
  104. package/webapp/src/components/queues/QueueFilters.vue +90 -0
  105. package/webapp/src/composables/useApi.js +34 -0
  106. package/webapp/src/composables/useTheme.js +36 -0
  107. package/webapp/src/main.js +11 -0
  108. package/webapp/src/router/index.js +42 -0
  109. package/webapp/src/utils/colors.js +96 -0
  110. package/webapp/src/utils/formatters.js +49 -0
  111. package/webapp/src/views/Analytics.vue +377 -0
  112. package/webapp/src/views/ConsumerGroups.vue +433 -0
  113. package/webapp/src/views/Dashboard.vue +418 -0
  114. package/webapp/src/views/Messages.vue +363 -0
  115. package/webapp/src/views/QueueDetail.vue +582 -0
  116. package/webapp/src/views/Queues.vue +496 -0
  117. package/webapp/tailwind.config.js +25 -0
  118. package/webapp/vite.config.js +10 -0
  119. package/dashboard/.vscode/extensions.json +0 -3
  120. package/dashboard/README.md +0 -5
  121. package/dashboard/index.html +0 -14
  122. package/dashboard/package-lock.json +0 -1458
  123. package/dashboard/package.json +0 -25
  124. package/dashboard/public/vite.svg +0 -1
  125. package/dashboard/src/App.vue +0 -29
  126. package/dashboard/src/assets/styles/main.css +0 -908
  127. package/dashboard/src/assets/vue.svg +0 -1
  128. package/dashboard/src/components/cards/MetricCard.vue +0 -298
  129. package/dashboard/src/components/charts/QueueDepthChart.vue +0 -276
  130. package/dashboard/src/components/charts/QueueLagChart.vue +0 -436
  131. package/dashboard/src/components/charts/ThroughputChart.vue +0 -302
  132. package/dashboard/src/components/common/ActivityFeed.vue +0 -251
  133. package/dashboard/src/components/layout/AppHeader.vue +0 -208
  134. package/dashboard/src/components/layout/AppLayout.vue +0 -88
  135. package/dashboard/src/components/layout/AppSidebar.vue +0 -261
  136. package/dashboard/src/main.js +0 -44
  137. package/dashboard/src/router.js +0 -54
  138. package/dashboard/src/services/api.js +0 -187
  139. package/dashboard/src/services/websocket.js +0 -167
  140. package/dashboard/src/utils/constants.js +0 -56
  141. package/dashboard/src/utils/helpers.js +0 -118
  142. package/dashboard/src/views/Analytics.vue +0 -912
  143. package/dashboard/src/views/Dashboard.vue +0 -906
  144. package/dashboard/src/views/Messages.vue +0 -437
  145. package/dashboard/src/views/QueueDetail.vue +0 -501
  146. package/dashboard/src/views/Queues.vue +0 -333
  147. package/dashboard/vite.config.js +0 -30
  148. package/src/client/queenClient.js +0 -513
  149. package/src/routes/analytics.js +0 -812
@@ -0,0 +1,377 @@
1
+ <template>
2
+ <div class="page-flat">
3
+ <div class="py-4 px-3">
4
+ <div class="space-y-2.5">
5
+ <!-- Filters -->
6
+ <div class="filter-flat">
7
+ <AnalyticsFilters
8
+ v-model:time-range="timeRange"
9
+ v-model:queue="queueFilter"
10
+ v-model:namespace="namespaceFilter"
11
+ v-model:task="taskFilter"
12
+ :queues="allQueues"
13
+ :namespaces="namespaces"
14
+ :tasks="tasks"
15
+ />
16
+ </div>
17
+
18
+ <LoadingSpinner v-if="loading && !statusData" />
19
+
20
+ <div v-else-if="error" class="error-flat">
21
+ <p><strong>Error loading analytics:</strong> {{ error }}</p>
22
+ </div>
23
+
24
+ <template v-else>
25
+ <!-- Active Filters Display -->
26
+ <div v-if="queueFilter || namespaceFilter || taskFilter" class="flex flex-wrap gap-2">
27
+ <span v-if="queueFilter" class="badge badge-info">
28
+ Queue: {{ queueFilter }}
29
+ </span>
30
+ <span v-if="namespaceFilter" class="badge badge-info">
31
+ Namespace: {{ namespaceFilter }}
32
+ </span>
33
+ <span v-if="taskFilter" class="badge badge-info">
34
+ Task: {{ taskFilter }}
35
+ </span>
36
+ </div>
37
+
38
+ <!-- Message Flow Chart -->
39
+ <div class="chart-flat">
40
+ <div class="flex items-center gap-2 mb-2">
41
+ <div class="w-7 h-7 rounded-lg bg-gradient-to-br from-rose-500/10 to-purple-500/20 flex items-center justify-center">
42
+ <svg class="w-4 h-4 text-rose-600 dark:text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
+ <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" />
44
+ </svg>
45
+ </div>
46
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Message Flow Over Time</h3>
47
+ </div>
48
+ <MessageFlowChart :data="statusData" />
49
+ </div>
50
+
51
+ <!-- Charts Row -->
52
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
53
+ <!-- Top Queues Chart -->
54
+ <div class="chart-flat">
55
+ <div class="flex items-center gap-2 mb-2">
56
+ <div class="w-7 h-7 rounded-lg bg-gradient-to-br from-indigo-500/10 to-violet-500/20 flex items-center justify-center">
57
+ <svg class="w-4 h-4 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
58
+ <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" />
59
+ </svg>
60
+ </div>
61
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Top Queues by Volume</h3>
62
+ </div>
63
+ <TopQueuesChart :queues="filteredQueues" />
64
+ </div>
65
+
66
+ <!-- Message Distribution Chart -->
67
+ <div class="chart-flat">
68
+ <div class="flex items-center gap-2 mb-2">
69
+ <div class="w-7 h-7 rounded-lg bg-gradient-to-br from-green-500/10 to-emerald-500/20 flex items-center justify-center">
70
+ <svg class="w-4 h-4 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" />
72
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" />
73
+ </svg>
74
+ </div>
75
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Message Status Distribution</h3>
76
+ </div>
77
+ <MessageDistributionChart :messages="statusData?.messages" />
78
+ </div>
79
+ </div>
80
+
81
+ <!-- Performance Metrics -->
82
+ <div class="info-flat">
83
+ <div class="flex items-center gap-2 mb-2">
84
+ <div class="w-7 h-7 rounded-lg bg-gradient-to-br from-blue-500/10 to-cyan-500/20 flex items-center justify-center">
85
+ <svg class="w-4 h-4 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
86
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
87
+ </svg>
88
+ </div>
89
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Performance Metrics</h3>
90
+ </div>
91
+ <AnalyticsMetrics :data="statusData" :messages="statusData?.messages" />
92
+ </div>
93
+
94
+ <!-- Detailed Stats -->
95
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
96
+ <!-- Leases Info -->
97
+ <div class="info-flat">
98
+ <div class="flex items-center gap-2 mb-2">
99
+ <div class="w-7 h-7 rounded-lg bg-gradient-to-br from-purple-500/10 to-indigo-500/20 flex items-center justify-center">
100
+ <svg class="w-4 h-4 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
101
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
102
+ </svg>
103
+ </div>
104
+ <h3 class="text-sm font-semibold">Active Leases</h3>
105
+ </div>
106
+ <div class="space-y-2.5">
107
+ <div class="flex items-center justify-between text-sm">
108
+ <span class="text-gray-600 dark:text-gray-400">Active Leases</span>
109
+ <span class="font-semibold">{{ statusData?.leases?.active || 0 }}</span>
110
+ </div>
111
+ <div class="flex items-center justify-between text-sm">
112
+ <span class="text-gray-600 dark:text-gray-400">Partitions with Leases</span>
113
+ <span class="font-semibold">{{ statusData?.leases?.partitionsWithLeases || 0 }}</span>
114
+ </div>
115
+ <div class="flex items-center justify-between text-sm">
116
+ <span class="text-gray-600 dark:text-gray-400">Total Batch Size</span>
117
+ <span class="font-semibold">{{ statusData?.leases?.totalBatchSize || 0 }}</span>
118
+ </div>
119
+ <div class="flex items-center justify-between text-sm">
120
+ <span class="text-gray-600 dark:text-gray-400">Total Acked</span>
121
+ <span class="font-semibold">{{ statusData?.leases?.totalAcked || 0 }}</span>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Dead Letter Queue -->
127
+ <div class="info-flat">
128
+ <div class="flex items-center gap-2 mb-2">
129
+ <div class="w-7 h-7 rounded-lg bg-gradient-to-br from-red-500/10 to-rose-500/20 flex items-center justify-center">
130
+ <svg class="w-4 h-4 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
131
+ <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" />
132
+ </svg>
133
+ </div>
134
+ <h3 class="text-sm font-semibold">Dead Letter Queue</h3>
135
+ </div>
136
+ <div class="space-y-2.5">
137
+ <div class="flex items-center justify-between text-sm">
138
+ <span class="text-gray-600 dark:text-gray-400">Total Messages</span>
139
+ <span class="font-semibold">{{ statusData?.deadLetterQueue?.totalMessages || 0 }}</span>
140
+ </div>
141
+ <div class="flex items-center justify-between text-sm">
142
+ <span class="text-gray-600 dark:text-gray-400">Affected Partitions</span>
143
+ <span class="font-semibold">{{ statusData?.deadLetterQueue?.affectedPartitions || 0 }}</span>
144
+ </div>
145
+ </div>
146
+
147
+ <div v-if="statusData?.deadLetterQueue?.topErrors?.length" class="mt-4 pt-4 border-t border-gray-200/30 dark:border-gray-700/30">
148
+ <p class="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2">Top Errors:</p>
149
+ <div class="space-y-2">
150
+ <div
151
+ v-for="(errorItem, idx) in statusData.deadLetterQueue.topErrors.slice(0, 3)"
152
+ :key="idx"
153
+ class="text-xs"
154
+ >
155
+ <div class="flex items-center justify-between">
156
+ <span class="text-gray-600 dark:text-gray-400 truncate flex-1">{{ errorItem.error }}</span>
157
+ <span class="font-semibold ml-2">{{ errorItem.count }}</span>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </template>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </template>
169
+
170
+ <script setup>
171
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
172
+ import { analyticsApi } from '../api/analytics';
173
+ import { queuesApi } from '../api/queues';
174
+ import { resourcesApi } from '../api/resources';
175
+
176
+ import AnalyticsFilters from '../components/analytics/AnalyticsFilters.vue';
177
+ import MessageFlowChart from '../components/analytics/MessageFlowChart.vue';
178
+ import TopQueuesChart from '../components/analytics/TopQueuesChart.vue';
179
+ import MessageDistributionChart from '../components/analytics/MessageDistributionChart.vue';
180
+ import AnalyticsMetrics from '../components/analytics/AnalyticsMetrics.vue';
181
+ import LoadingSpinner from '../components/common/LoadingSpinner.vue';
182
+
183
+ const loading = ref(false);
184
+ const error = ref(null);
185
+ const timeRange = ref('1h');
186
+ const queueFilter = ref('');
187
+ const namespaceFilter = ref('');
188
+ const taskFilter = ref('');
189
+ const statusData = ref(null);
190
+ const allQueues = ref([]);
191
+ const namespaces = ref([]);
192
+ const tasks = ref([]);
193
+
194
+ const filteredQueues = computed(() => {
195
+ let queues = allQueues.value;
196
+
197
+ if (queueFilter.value) {
198
+ queues = queues.filter(q => q.name === queueFilter.value);
199
+ }
200
+ if (namespaceFilter.value) {
201
+ queues = queues.filter(q => q.namespace === namespaceFilter.value);
202
+ }
203
+ if (taskFilter.value) {
204
+ queues = queues.filter(q => q.task === taskFilter.value);
205
+ }
206
+
207
+ return queues;
208
+ });
209
+
210
+ async function loadData() {
211
+ loading.value = true;
212
+ error.value = null;
213
+
214
+ try {
215
+ const now = new Date();
216
+ const from = new Date(now);
217
+
218
+ switch (timeRange.value) {
219
+ case '1h':
220
+ from.setHours(from.getHours() - 1);
221
+ break;
222
+ case '6h':
223
+ from.setHours(from.getHours() - 6);
224
+ break;
225
+ case '24h':
226
+ from.setHours(from.getHours() - 24);
227
+ break;
228
+ case '7d':
229
+ from.setDate(from.getDate() - 7);
230
+ break;
231
+ }
232
+
233
+ const params = {
234
+ from: from.toISOString(),
235
+ to: now.toISOString(),
236
+ };
237
+
238
+ if (queueFilter.value) params.queue = queueFilter.value;
239
+ if (namespaceFilter.value) params.namespace = namespaceFilter.value;
240
+ if (taskFilter.value) params.task = taskFilter.value;
241
+
242
+ const [statusRes, queuesRes, namespacesRes, tasksRes] = await Promise.all([
243
+ analyticsApi.getStatus(params),
244
+ queuesApi.getQueues(),
245
+ resourcesApi.getNamespaces(),
246
+ resourcesApi.getTasks(),
247
+ ]);
248
+
249
+ statusData.value = statusRes.data;
250
+ allQueues.value = queuesRes.data.queues;
251
+ namespaces.value = namespacesRes.data.namespaces;
252
+ tasks.value = tasksRes.data.tasks;
253
+ } catch (err) {
254
+ error.value = err.message;
255
+ console.error('Analytics error:', err);
256
+ } finally {
257
+ loading.value = false;
258
+ }
259
+ }
260
+
261
+ watch([timeRange, queueFilter, namespaceFilter, taskFilter], () => {
262
+ loadData();
263
+ });
264
+
265
+ onMounted(() => {
266
+ loadData();
267
+
268
+ if (window.registerRefreshCallback) {
269
+ window.registerRefreshCallback('/analytics', loadData);
270
+ }
271
+ });
272
+
273
+ onUnmounted(() => {
274
+ if (window.registerRefreshCallback) {
275
+ window.registerRefreshCallback('/analytics', null);
276
+ }
277
+ });
278
+ </script>
279
+
280
+ <style scoped>
281
+ .page-flat {
282
+ min-height: 100%;
283
+ }
284
+
285
+ .chart-flat {
286
+ background: #ffffff;
287
+ border: none;
288
+ box-shadow: none;
289
+ border-radius: 0.75rem;
290
+ padding: 1rem;
291
+ transition: all 0.3s ease;
292
+ }
293
+
294
+ .dark .chart-flat {
295
+ background: #0a0d14;
296
+ }
297
+
298
+ .chart-flat:hover {
299
+ background: #fafafa;
300
+ }
301
+
302
+ .dark .chart-flat:hover {
303
+ background: #0d1117;
304
+ }
305
+
306
+ /* Flat filters */
307
+ .filter-flat {
308
+ background: #ffffff;
309
+ border: none;
310
+ box-shadow: none;
311
+ border-radius: 0.75rem;
312
+ padding: 1rem;
313
+ }
314
+
315
+ .dark .filter-flat {
316
+ background: #0a0d14;
317
+ }
318
+
319
+
320
+ .filter-flat :deep(.input) {
321
+ background: transparent;
322
+ border: 1px solid rgba(156, 163, 175, 0.15);
323
+ transition: all 0.2s ease;
324
+ }
325
+
326
+ .filter-flat :deep(.input:hover) {
327
+ border-color: rgba(156, 163, 175, 0.25);
328
+ }
329
+
330
+ .filter-flat :deep(.input:focus) {
331
+ background: rgba(244, 63, 94, 0.02);
332
+ border-color: rgba(244, 63, 94, 0.4);
333
+ box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.05);
334
+ }
335
+
336
+ .dark .filter-flat :deep(.input) {
337
+ border-color: rgba(156, 163, 175, 0.1);
338
+ }
339
+
340
+ .dark .filter-flat :deep(.input:focus) {
341
+ background: rgba(244, 63, 94, 0.03);
342
+ border-color: rgba(244, 63, 94, 0.5);
343
+ box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.08);
344
+ }
345
+
346
+ .info-flat {
347
+ background: #ffffff;
348
+ border: none;
349
+ box-shadow: none;
350
+ border-radius: 0.75rem;
351
+ padding: 1rem;
352
+ transition: all 0.3s ease;
353
+ }
354
+
355
+ .dark .info-flat {
356
+ background: #0a0d14;
357
+ }
358
+
359
+ .info-flat:hover {
360
+ background: #fafafa;
361
+ }
362
+
363
+ .dark .info-flat:hover {
364
+ background: #0d1117;
365
+ }
366
+
367
+ .error-flat {
368
+ background: transparent;
369
+ color: #dc2626;
370
+ font-size: 0.875rem;
371
+ padding: 1rem;
372
+ }
373
+
374
+ .dark .error-flat {
375
+ color: #fca5a5;
376
+ }
377
+ </style>