jettask 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

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 (93) hide show
  1. jettask/monitor/run_backlog_collector.py +96 -0
  2. jettask/monitor/stream_backlog_monitor.py +362 -0
  3. jettask/pg_consumer/pg_consumer_v2.py +403 -0
  4. jettask/pg_consumer/sql_utils.py +182 -0
  5. jettask/scheduler/__init__.py +17 -0
  6. jettask/scheduler/add_execution_count.sql +11 -0
  7. jettask/scheduler/add_priority_field.sql +26 -0
  8. jettask/scheduler/add_scheduler_id.sql +25 -0
  9. jettask/scheduler/add_scheduler_id_index.sql +10 -0
  10. jettask/scheduler/loader.py +249 -0
  11. jettask/scheduler/make_scheduler_id_required.sql +28 -0
  12. jettask/scheduler/manager.py +696 -0
  13. jettask/scheduler/migrate_interval_seconds.sql +9 -0
  14. jettask/scheduler/models.py +200 -0
  15. jettask/scheduler/multi_namespace_scheduler.py +294 -0
  16. jettask/scheduler/performance_optimization.sql +45 -0
  17. jettask/scheduler/run_scheduler.py +186 -0
  18. jettask/scheduler/scheduler.py +715 -0
  19. jettask/scheduler/schema.sql +84 -0
  20. jettask/scheduler/unified_manager.py +450 -0
  21. jettask/scheduler/unified_scheduler_manager.py +280 -0
  22. jettask/webui/backend/api/__init__.py +3 -0
  23. jettask/webui/backend/api/v1/__init__.py +17 -0
  24. jettask/webui/backend/api/v1/monitoring.py +431 -0
  25. jettask/webui/backend/api/v1/namespaces.py +504 -0
  26. jettask/webui/backend/api/v1/queues.py +342 -0
  27. jettask/webui/backend/api/v1/tasks.py +367 -0
  28. jettask/webui/backend/core/__init__.py +3 -0
  29. jettask/webui/backend/core/cache.py +221 -0
  30. jettask/webui/backend/core/database.py +200 -0
  31. jettask/webui/backend/core/exceptions.py +102 -0
  32. jettask/webui/backend/models/__init__.py +3 -0
  33. jettask/webui/backend/models/requests.py +236 -0
  34. jettask/webui/backend/models/responses.py +230 -0
  35. jettask/webui/backend/services/__init__.py +3 -0
  36. jettask/webui/frontend/index.html +13 -0
  37. jettask/webui/models/__init__.py +3 -0
  38. jettask/webui/models/namespace.py +63 -0
  39. jettask/webui/sql/batch_upsert_functions.sql +178 -0
  40. jettask/webui/sql/init_database.sql +640 -0
  41. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/METADATA +80 -10
  42. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/RECORD +46 -53
  43. jettask/webui/frontend/package-lock.json +0 -4833
  44. jettask/webui/frontend/package.json +0 -30
  45. jettask/webui/frontend/src/App.css +0 -109
  46. jettask/webui/frontend/src/App.jsx +0 -66
  47. jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
  48. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
  49. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
  50. jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
  51. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
  52. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
  53. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
  54. jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
  55. jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
  56. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
  57. jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
  58. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
  59. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
  60. jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
  61. jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
  62. jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
  63. jettask/webui/frontend/src/components/layout/Header.css +0 -106
  64. jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
  65. jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
  66. jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
  67. jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
  68. jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
  69. jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
  70. jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
  71. jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
  72. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
  73. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
  74. jettask/webui/frontend/src/index.css +0 -114
  75. jettask/webui/frontend/src/main.jsx +0 -20
  76. jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
  77. jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
  78. jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
  79. jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
  80. jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
  81. jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
  82. jettask/webui/frontend/src/pages/Queues.jsx +0 -12
  83. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -809
  84. jettask/webui/frontend/src/pages/Settings.jsx +0 -800
  85. jettask/webui/frontend/src/pages/Workers.jsx +0 -12
  86. jettask/webui/frontend/src/services/api.js +0 -114
  87. jettask/webui/frontend/src/services/queueTrend.js +0 -152
  88. jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
  89. jettask/webui/frontend/src/utils/userPreferences.js +0 -154
  90. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/WHEEL +0 -0
  91. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/entry_points.txt +0 -0
  92. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/licenses/LICENSE +0 -0
  93. {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/top_level.txt +0 -0
@@ -1,638 +0,0 @@
1
- import { useState, useEffect, useCallback, useMemo } from 'react';
2
- import { Card, Spin, Empty, Space, message, Row, Col, Statistic, Alert } from 'antd';
3
- import { Line, Column } from '@ant-design/plots';
4
- import { WarningOutlined } from '@ant-design/icons';
5
- import { useNamespace } from '../contexts/NamespaceContext';
6
- import dayjs from 'dayjs';
7
- import axios from 'axios';
8
-
9
- // 时间范围选项
10
- const TIME_RANGES = {
11
- '15m': { label: '15分钟', minutes: 15, granularity: 'minute' },
12
- '30m': { label: '30分钟', minutes: 30, granularity: 'minute' },
13
- '1h': { label: '1小时', minutes: 60, granularity: 'minute' },
14
- '3h': { label: '3小时', minutes: 180, granularity: '5minute' },
15
- '6h': { label: '6小时', minutes: 360, granularity: '10minute' },
16
- '12h': { label: '12小时', minutes: 720, granularity: '30minute' },
17
- '24h': { label: '24小时', minutes: 1440, granularity: 'hour' },
18
- '3d': { label: '3天', minutes: 4320, granularity: 'hour' },
19
- '7d': { label: '7天', minutes: 10080, granularity: 'hour' },
20
- };
21
-
22
- // 告警阈值配置
23
- const ALERT_THRESHOLDS = {
24
- warning: 1000, // 警告阈值
25
- critical: 5000, // 严重阈值
26
- };
27
-
28
- function QueueBacklogTrend({
29
- height = 240, // 默认高度改为240,与QueueDetail中的流量速率图表一致
30
- showTitle = true,
31
- defaultTimeRange = '1h',
32
- autoRefresh = true,
33
- refreshInterval = 60000,
34
- showStatistics = true,
35
- chartType = 'line', // 'line' or 'column'
36
- onAlertTriggered = null,
37
- selectedQueues: propSelectedQueues = null, // 可以从props传入选中的队列
38
- }) {
39
- const { currentNamespace } = useNamespace();
40
- const [loading, setLoading] = useState(false);
41
- // 直接使用props中的defaultTimeRange,不使用内部状态
42
- const timeRange = defaultTimeRange;
43
- const [selectedQueues, setSelectedQueues] = useState(propSelectedQueues === null ? [] : propSelectedQueues || []);
44
- const [, setAvailableQueues] = useState([]);
45
-
46
- const [chartData, setChartData] = useState([]);
47
- const [isChartReady, setIsChartReady] = useState(false);
48
- const [statistics, setStatistics] = useState({
49
- total: 0,
50
- max: 0,
51
- avg: 0,
52
- trend: 'stable', // 'up', 'down', 'stable'
53
- alertQueues: [],
54
- });
55
- const [viewType] = useState(chartType);
56
- const [, setLastUpdateTime] = useState(null);
57
-
58
- // 计算统计信息
59
- const calculateStatistics = useCallback((data) => {
60
- if (!data || data.length === 0) {
61
- setStatistics({
62
- total: 0,
63
- max: 0,
64
- avg: 0,
65
- trend: 'stable',
66
- alertQueues: [],
67
- });
68
- return;
69
- }
70
-
71
- // 获取最新时间点的数据
72
- const latestTime = Math.max(...data.map(d => new Date(d.timestamp).getTime()));
73
- const latestData = data.filter(d => new Date(d.timestamp).getTime() === latestTime);
74
-
75
- // 计算总积压
76
- const total = latestData.reduce((sum, d) => sum + d.value, 0);
77
-
78
- // 计算最大值
79
- const max = Math.max(...data.map(d => d.value));
80
-
81
- // 计算平均值
82
- const avg = data.length > 0 ? data.reduce((sum, d) => sum + d.value, 0) / data.length : 0;
83
-
84
- // 分析趋势(比较最近和之前的数据)
85
- const timePoints = [...new Set(data.map(d => d.timestamp))].sort();
86
- let trend = 'stable';
87
- if (timePoints.length >= 2) {
88
- const recentTime = timePoints[timePoints.length - 1];
89
- const previousTime = timePoints[Math.max(0, timePoints.length - 6)]; // 对比5个时间点前
90
-
91
- const recentTotal = data
92
- .filter(d => d.timestamp === recentTime)
93
- .reduce((sum, d) => sum + d.value, 0);
94
-
95
- const previousTotal = data
96
- .filter(d => d.timestamp === previousTime)
97
- .reduce((sum, d) => sum + d.value, 0);
98
-
99
- const diff = recentTotal - previousTotal;
100
- const diffPercent = previousTotal > 0 ? (diff / previousTotal) * 100 : 0;
101
-
102
- if (diffPercent > 20) trend = 'up';
103
- else if (diffPercent < -20) trend = 'down';
104
- }
105
-
106
- // 检查告警队列
107
- const alertQueues = [];
108
- selectedQueues.forEach(queue => {
109
- const queueData = latestData.find(d => d.queue === queue);
110
- if (queueData) {
111
- if (queueData.value >= ALERT_THRESHOLDS.critical) {
112
- alertQueues.push({ queue, level: 'critical', value: queueData.value });
113
- } else if (queueData.value >= ALERT_THRESHOLDS.warning) {
114
- alertQueues.push({ queue, level: 'warning', value: queueData.value });
115
- }
116
- }
117
- });
118
-
119
- // 触发告警回调
120
- if (onAlertTriggered && alertQueues.length > 0) {
121
- onAlertTriggered(alertQueues);
122
- }
123
-
124
- setStatistics({
125
- total: Math.round(total),
126
- max: Math.round(max),
127
- avg: Math.round(avg),
128
- trend,
129
- alertQueues,
130
- });
131
- }, [selectedQueues, onAlertTriggered]);
132
-
133
- // 直接获取积压数据,不依赖selectedQueues状态
134
- const fetchBacklogDataDirectly = async (queueList) => {
135
- console.log('fetchBacklogDataDirectly called with:', queueList);
136
- const validQueues = queueList.filter(q => q != null && q !== '');
137
- console.log('validQueues:', validQueues);
138
- if (validQueues.length === 0) {
139
- console.log('No valid queues, returning early');
140
- return;
141
- }
142
-
143
- setLoading(true);
144
- setIsChartReady(false);
145
- try {
146
- const namespace = currentNamespace || 'default';
147
-
148
- const response = await axios.get(`/api/stream-backlog/${namespace}`, {
149
- params: {
150
- hours: Math.ceil(TIME_RANGES[timeRange].minutes / 60) || 1
151
- }
152
- });
153
-
154
- console.log('Backlog API Response:', response.status, response.data);
155
-
156
- if (response.data.success) {
157
- const data = response.data.data;
158
- const backlogData = [];
159
-
160
- if (data && Array.isArray(data)) {
161
- data.forEach(item => {
162
- backlogData.push({
163
- time: dayjs(item.created_at).format('HH:mm:ss'),
164
- timestamp: item.created_at,
165
- queue: item.stream_name,
166
- series: item.consumer_group || item.stream_name,
167
- value: item.backlog_unprocessed || 0,
168
- consumer_group: item.consumer_group,
169
- });
170
- });
171
- }
172
-
173
- console.log('Processed chart data:', backlogData);
174
- setChartData(backlogData);
175
- setTimeout(() => setIsChartReady(true), 100);
176
-
177
- if (backlogData.length > 0) {
178
- calculateStatistics(backlogData);
179
- } else {
180
- setStatistics({
181
- total: 0,
182
- max: 0,
183
- avg: 0,
184
- trend: 'stable',
185
- alertQueues: [],
186
- });
187
- }
188
-
189
- setLastUpdateTime(dayjs());
190
- }
191
- } catch (error) {
192
- console.error('Failed to fetch backlog data:', error);
193
- } finally {
194
- setLoading(false);
195
- }
196
- };
197
-
198
- // 获取队列积压数据(使用指定的队列列表)
199
- const fetchBacklogDataWithQueues = useCallback(async (queueList) => {
200
- return fetchBacklogDataDirectly(queueList);
201
- }, []);
202
-
203
- // 获取队列积压数据
204
- const fetchBacklogData = useCallback(async () => {
205
- return fetchBacklogDataWithQueues(selectedQueues);
206
- }, [selectedQueues, fetchBacklogDataWithQueues]);
207
-
208
- // 获取可用队列列表
209
- const fetchAvailableQueues = useCallback(async () => {
210
- console.log('fetchAvailableQueues called');
211
- try {
212
- const namespace = currentNamespace || 'default';
213
- console.log('Using namespace:', namespace);
214
- const response = await axios.get(`/api/queues/${namespace}`);
215
- console.log('Queues API response:', response);
216
- if (response.data.success) {
217
- // 过滤掉name为null或undefined的队列
218
- const queues = response.data.data;
219
- console.log('Raw queues data:', queues);
220
- setAvailableQueues(queues);
221
-
222
- // 如果没有通过props传入选中的队列,则根据propSelectedQueues判断选择策略
223
- if (!propSelectedQueues || propSelectedQueues.length === 0) {
224
- console.log('Processing queue selection logic...');
225
- // 如果propSelectedQueues为null,选择所有队列;否则选择积压最多的前5个队列
226
- const allQueues = response.data.data
227
- .filter(q => q && q.name != null && q.name !== '')
228
- .map(q => q.name);
229
- console.log('All available queues:', allQueues);
230
-
231
- const targetQueues = propSelectedQueues === null ?
232
- allQueues : // 选择所有队列
233
- response.data.data
234
- .filter(q => q && q.name != null && q.name !== '')
235
- .sort((a, b) => (b.pending || 0) - (a.pending || 0))
236
- .slice(0, 5)
237
- .map(q => q.name); // 选择积压最多的前5个队列
238
-
239
- console.log('Target queues to select:', targetQueues);
240
- console.log('Current selectedQueues.length:', selectedQueues.length);
241
-
242
- if (selectedQueues.length === 0 && targetQueues.length > 0) {
243
- console.log('✅ Setting selectedQueues to:', targetQueues);
244
- setSelectedQueues(targetQueues);
245
- // 立即触发数据获取,使用新的队列列表
246
- setTimeout(async () => {
247
- console.log('🚀 Calling fetchBacklogDataWithQueues with:', targetQueues);
248
- // 直接调用数据获取逻辑,避免依赖循环
249
- await fetchBacklogDataDirectly(targetQueues);
250
- }, 100);
251
- } else {
252
- console.log('❌ Conditions not met for setting queues:', {
253
- selectedQueuesLength: selectedQueues.length,
254
- targetQueuesLength: targetQueues.length,
255
- propSelectedQueues,
256
- condition1: selectedQueues.length === 0,
257
- condition2: targetQueues.length > 0
258
- });
259
- }
260
- } else {
261
- console.log('Using provided propSelectedQueues:', propSelectedQueues);
262
- }
263
- }
264
- } catch (error) {
265
- console.error('Failed to fetch queues:', error);
266
- }
267
- }, [currentNamespace, selectedQueues.length, propSelectedQueues]);
268
-
269
- // 初始化
270
- useEffect(() => {
271
- fetchAvailableQueues();
272
- }, [fetchAvailableQueues]);
273
-
274
- // 处理通过props传入的选中队列
275
- useEffect(() => {
276
- if (propSelectedQueues && propSelectedQueues.length > 0) {
277
- setSelectedQueues(propSelectedQueues);
278
- }
279
- }, [propSelectedQueues]);
280
-
281
- // 当选中队列或时间范围变化时,获取数据
282
- useEffect(() => {
283
- console.log('selectedQueues changed:', selectedQueues, 'length:', selectedQueues.length);
284
- if (selectedQueues.length > 0) {
285
- console.log('Calling fetchBacklogData()');
286
- fetchBacklogData();
287
- } else {
288
- console.log('selectedQueues is empty, not calling fetchBacklogData');
289
- }
290
- }, [selectedQueues, timeRange, fetchBacklogData]);
291
-
292
- // 自动刷新
293
- useEffect(() => {
294
- if (!autoRefresh || selectedQueues.length === 0) return;
295
-
296
- const timer = setInterval(() => {
297
- fetchBacklogData();
298
- }, refreshInterval);
299
-
300
- return () => clearInterval(timer);
301
- }, [autoRefresh, refreshInterval, fetchBacklogData, selectedQueues.length]);
302
-
303
- // 图表配置
304
- const lineConfig = useMemo(() => {
305
- // 调试日志
306
- console.log('Creating lineConfig with chartData:', chartData);
307
- console.log('Chart data length:', chartData ? chartData.length : 0);
308
-
309
- // 确保数据有效
310
- if (!chartData || chartData.length === 0) {
311
- console.log('Chart data is empty, returning minimal config');
312
- return {
313
- data: [],
314
- xField: 'time',
315
- yField: 'value',
316
- height: height,
317
- };
318
- }
319
-
320
- // 验证数据格式
321
- const sampleData = chartData[0];
322
- console.log('Sample data item:', sampleData);
323
- console.log('Has time field:', 'time' in sampleData);
324
- console.log('Has value field:', 'value' in sampleData);
325
- console.log('Has series field:', 'series' in sampleData);
326
-
327
- // 定义颜色方案
328
- const colorPalette = [
329
- '#5B8FF9', // 蓝色
330
- '#5AD8A6', // 绿色
331
- '#5D7092', // 灰蓝色
332
- '#F6BD16', // 黄色
333
- '#E8684A', // 橙红色
334
- '#6DC8EC', // 天蓝色
335
- '#9270CA', // 紫色
336
- '#FF9D4D', // 橙色
337
- '#269A99', // 青色
338
- '#FF99C3', // 粉色
339
- '#7B9DF0', // 浅蓝色
340
- '#AAD977', // 黄绿色
341
- '#D97E84', // 玫瑰色
342
- '#9FD3E8', // 浅青色
343
- '#BFBFBF', // 灰色
344
- ];
345
-
346
- // 使用最基础的配置,避免复杂特性导致的错误
347
- const config = {
348
- data: chartData,
349
- xField: 'time',
350
- yField: 'value',
351
- seriesField: 'series', // 关键:必须使用seriesField来区分不同的线
352
- colorField: 'series', // 使用series字段来区分颜色
353
- smooth: true, // 启用平滑曲线,避免突兀的转折
354
- animation: false, // 关闭动画避免性能问题
355
- height: height, // 设置图表高度
356
- // 配置颜色 - 确保每个series有不同的颜色
357
- color: colorPalette,
358
- // 简单的点样式
359
- // point: {
360
- // size: 3,
361
- // shape: 'circle',
362
- // },
363
- // X轴配置 - 处理时间显示
364
- xAxis: {
365
- type: 'cat', // 使用分类轴,因为时间已经格式化为字符串
366
- label: {
367
- autoRotate: true,
368
- autoHide: true,
369
- autoEllipsis: true,
370
- },
371
- },
372
- // Y轴配置
373
- yAxis: {
374
- min: 0,
375
- label: {
376
- formatter: (v) => {
377
- return `${parseInt(v)}`;
378
- },
379
- },
380
- },
381
- legend: {
382
- position: 'top',
383
- itemName: {
384
- style: {
385
- fontSize: 12,
386
- },
387
- },
388
- },
389
- // tooltip: {
390
- // showCrosshairs: true,
391
- // shared: true,
392
- // showMarkers: true,
393
- // },
394
- // 线条样式
395
- lineStyle: {
396
- lineWidth: 2,
397
- },
398
- // 连接空值
399
- connectNulls: true,
400
- };
401
-
402
- console.log('Final line config:', config);
403
- return config;
404
- }, [chartData, height]);
405
-
406
- const columnConfig = useMemo(() => {
407
- // 确保lineConfig有效
408
- if (!lineConfig || !lineConfig.data || lineConfig.data.length === 0) {
409
- return {
410
- data: [],
411
- xField: 'time',
412
- yField: 'value',
413
- height: height,
414
- };
415
- }
416
-
417
- return {
418
- ...lineConfig,
419
- isGroup: true,
420
- columnStyle: {
421
- radius: [4, 4, 0, 0],
422
- },
423
- // 柱状图特有的配置
424
- columnWidthRatio: 0.8,
425
- dodgePadding: 2,
426
- };
427
- }, [lineConfig, height]);
428
-
429
- // 趋势图标
430
- const getTrendIcon = () => {
431
- const { trend } = statistics;
432
- if (trend === 'up') return '↑';
433
- if (trend === 'down') return '↓';
434
- return '→';
435
- };
436
-
437
- const getTrendColor = () => {
438
- const { trend } = statistics;
439
- if (trend === 'up') return '#f50';
440
- if (trend === 'down') return '#52c41a';
441
- return '#1890ff';
442
- };
443
-
444
- return (
445
- <>
446
- <Card
447
- title={showTitle ? "队列积压监控" : null}
448
- size="small"
449
- styles={{
450
- body: {
451
- padding: showTitle ? '12px' : '0px', // 无标题时不要padding
452
- height: 'auto', // 让高度自适应
453
- overflow: 'hidden'
454
- }
455
- }}
456
- style={{
457
- // 如果没有标题和统计信息,设置固定高度
458
- height: !showTitle && !showStatistics ? height : 'auto',
459
- }}
460
- // extra={
461
- // <Space>
462
- // {lastUpdateTime && (
463
- // <span style={{ fontSize: 12, color: '#999' }}>
464
- // 更新: {lastUpdateTime.format('HH:mm:ss')}
465
- // </span>
466
- // )}
467
- // <Select
468
- // value={timeRange}
469
- // onChange={setTimeRange}
470
- // style={{ width: 100 }}
471
- // size="small"
472
- // >
473
- // {Object.entries(TIME_RANGES).map(([key, { label }]) => (
474
- // <Option key={key} value={key}>{label}</Option>
475
- // ))}
476
- // </Select>
477
- // {(!propSelectedQueues || propSelectedQueues.length === 0) && (
478
- // <Select
479
- // mode="multiple"
480
- // placeholder="选择队列"
481
- // value={selectedQueues.filter(q => q != null && q !== '')}
482
- // onChange={(values) => {
483
- // // 过滤掉null和空字符串
484
- // const validValues = values.filter(v => v != null && v !== '');
485
- // setSelectedQueues(validValues);
486
- // }}
487
- // style={{ minWidth: 200, maxWidth: 400 }}
488
- // size="small"
489
- // maxTagCount={2}
490
- // >
491
- // {availableQueues
492
- // .filter(queue => queue != null && queue !== '')
493
- // .map(queue => (
494
- // <Option key={queue} value={queue}>{queue}</Option>
495
- // ))}
496
- // </Select>
497
- // )}
498
- // <Space.Compact size="small">
499
- // <Button
500
- // icon={<LineChartOutlined />}
501
- // onClick={() => setViewType('line')}
502
- // type={viewType === 'line' ? 'primary' : 'default'}
503
- // />
504
- // <Button
505
- // icon={<BarChartOutlined />}
506
- // onClick={() => setViewType('column')}
507
- // type={viewType === 'column' ? 'primary' : 'default'}
508
- // />
509
- // </Space.Compact>
510
- // <Button
511
- // icon={<ReloadOutlined />}
512
- // size="small"
513
- // onClick={fetchBacklogData}
514
- // loading={loading}
515
- // />
516
- // </Space>
517
- // }
518
- >
519
- {showStatistics && statistics.alertQueues.length > 0 && (
520
- <Alert
521
- message={
522
- <Space>
523
- <WarningOutlined />
524
- <span>
525
- 检测到 {statistics.alertQueues.length} 个队列积压超过阈值:
526
- {statistics.alertQueues.map(({ queue, level, value }) => (
527
- <span key={queue} style={{ marginLeft: 8 }}>
528
- <strong>{queue}</strong>
529
- <span style={{ color: level === 'critical' ? '#f50' : '#fa8c16' }}>
530
- ({value.toLocaleString()})
531
- </span>
532
- </span>
533
- ))}
534
- </span>
535
- </Space>
536
- }
537
- type={statistics.alertQueues.some(a => a.level === 'critical') ? 'error' : 'warning'}
538
- showIcon
539
- closable
540
- style={{ marginBottom: 16 }}
541
- />
542
- )}
543
-
544
- {showStatistics && (
545
- <Row gutter={16} style={{ marginBottom: 16 }}>
546
- <Col span={6}>
547
- <Statistic
548
- title="当前总积压"
549
- value={statistics.total}
550
- suffix="个"
551
- />
552
- </Col>
553
- <Col span={6}>
554
- <Statistic
555
- title="最大积压"
556
- value={statistics.max}
557
- suffix="个"
558
- valueStyle={{ color: statistics.max > ALERT_THRESHOLDS.critical ? '#f50' : undefined }}
559
- />
560
- </Col>
561
- <Col span={6}>
562
- <Statistic
563
- title="平均积压"
564
- value={statistics.avg}
565
- suffix="个"
566
- />
567
- </Col>
568
- <Col span={6}>
569
- <Statistic
570
- title="积压趋势"
571
- value={getTrendIcon()}
572
- valueStyle={{ color: getTrendColor(), fontSize: 24 }}
573
- prefix={
574
- <span style={{ fontSize: 14, color: '#999' }}>
575
- {statistics.trend === 'up' ? '上升' : statistics.trend === 'down' ? '下降' : '平稳'}
576
- </span>
577
- }
578
- />
579
- </Col>
580
- </Row>
581
- )}
582
-
583
- <Spin spinning={loading} >
584
- {(() => {
585
- // 调试日志
586
- console.log('Rendering chart section, loading:', loading, 'chartData length:', chartData?.length);
587
-
588
- if (loading) {
589
- // 加载中,Spin组件会显示loading状态
590
- return null;
591
- }
592
-
593
- if (!chartData || chartData.length === 0) {
594
- // 无数据时显示空状态
595
- return (
596
- <div style={{
597
- height: height,
598
- display: 'flex',
599
- alignItems: 'center',
600
- justifyContent: 'center'
601
- }}>
602
- <Empty
603
- description={selectedQueues.length === 0 ? "请选择要监控的队列" : "暂无数据"}
604
- style={{ margin: 0 }}
605
- />
606
- </div>
607
- );
608
- }
609
-
610
- // 有数据且图表准备就绪时才渲染
611
- if (!isChartReady) {
612
- console.log('Chart not ready yet, waiting...');
613
- return (
614
- <div style={{
615
- height: height,
616
- display: 'flex',
617
- alignItems: 'center',
618
- justifyContent: 'center'
619
- }}>
620
- <Spin />
621
- </div>
622
- );
623
- }
624
-
625
- console.log('Rendering chart with config:', lineConfig);
626
- return viewType === 'line' ? (
627
- <Line {...lineConfig} />
628
- ) : (
629
- <Column {...columnConfig} />
630
- );
631
- })()}
632
- </Spin>
633
- </Card>
634
- </>
635
- );
636
- }
637
-
638
- export default QueueBacklogTrend;
@@ -1,65 +0,0 @@
1
- /* 队列详情表格的紧凑样式 */
2
-
3
- /* 减少单元格内边距 */
4
- .compact-table .ant-table-cell {
5
- padding: 4px 8px !important;
6
- }
7
-
8
- /* 减少表头内边距 */
9
- .compact-table .ant-table-thead .ant-table-cell {
10
- padding: 6px 8px !important;
11
- background-color: #fafafa;
12
- }
13
-
14
- /* 减少分页器的边距 */
15
- .compact-table .ant-pagination {
16
- margin: 8px 0 !important;
17
- }
18
-
19
- /* 优化标签样式 */
20
- .compact-table .ant-tag {
21
- margin: 0;
22
- padding: 0 6px;
23
- line-height: 18px;
24
- font-size: 12px;
25
- }
26
-
27
- /* 鼠标悬停行高亮 */
28
- .compact-table .ant-table-tbody > tr:hover > td {
29
- background: #f5f5f5;
30
- }
31
-
32
- /* 移除不必要的边框 */
33
- .compact-table .ant-table {
34
- border: none;
35
- }
36
-
37
- /* Summary行样式 */
38
- .compact-table .ant-table-summary {
39
- background: #f0f0f0;
40
- }
41
-
42
- .compact-table .ant-table-summary .ant-table-cell {
43
- font-weight: 600;
44
- background: #f0f0f0;
45
- }
46
-
47
- /* 减少行高 */
48
- .compact-table .ant-table-tbody > tr {
49
- height: 36px;
50
- }
51
-
52
- /* 优化滚动条样式 */
53
- .compact-table .ant-table-body::-webkit-scrollbar {
54
- height: 6px;
55
- width: 6px;
56
- }
57
-
58
- .compact-table .ant-table-body::-webkit-scrollbar-thumb {
59
- background-color: rgba(0, 0, 0, 0.15);
60
- border-radius: 3px;
61
- }
62
-
63
- .compact-table .ant-table-body::-webkit-scrollbar-track {
64
- background-color: transparent;
65
- }