jettask 0.2.6__py3-none-any.whl → 0.2.8__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.
- jettask/core/cli.py +152 -0
- jettask/pg_consumer/sql/add_execution_time_field.sql +29 -0
- jettask/pg_consumer/sql/create_new_tables.sql +137 -0
- jettask/pg_consumer/sql/create_tables_v3.sql +175 -0
- jettask/pg_consumer/sql/migrate_to_new_structure.sql +179 -0
- jettask/pg_consumer/sql/modify_time_fields.sql +69 -0
- jettask/webui/frontend/package.json +30 -0
- jettask/webui/frontend/src/App.css +109 -0
- jettask/webui/frontend/src/App.jsx +66 -0
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
- jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
- jettask/webui/frontend/src/components/charts/QueueChart.jsx +111 -0
- jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +115 -0
- jettask/webui/frontend/src/components/charts/WorkerChart.jsx +40 -0
- jettask/webui/frontend/src/components/common/StatsCard.jsx +18 -0
- jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
- jettask/webui/frontend/src/components/layout/Header.css +106 -0
- jettask/webui/frontend/src/components/layout/Header.jsx +106 -0
- jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
- jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
- jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
- jettask/webui/frontend/src/contexts/LoadingContext.jsx +27 -0
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
- jettask/webui/frontend/src/index.css +114 -0
- jettask/webui/frontend/src/main.jsx +20 -0
- jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
- jettask/webui/frontend/src/pages/Dashboard/index.css +35 -0
- jettask/webui/frontend/src/pages/Dashboard/index.jsx +281 -0
- jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
- jettask/webui/frontend/src/pages/QueueDetail.jsx +1117 -0
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +527 -0
- jettask/webui/frontend/src/pages/Queues.jsx +12 -0
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
- jettask/webui/frontend/src/pages/Settings.jsx +800 -0
- jettask/webui/frontend/src/pages/Workers.jsx +12 -0
- jettask/webui/frontend/src/services/api.js +114 -0
- jettask/webui/frontend/src/services/queueTrend.js +152 -0
- jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
- jettask/webui/frontend/src/utils/userPreferences.js +154 -0
- jettask/webui/frontend/vite.config.js +26 -0
- {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/METADATA +70 -2
- {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/RECORD +59 -14
- jettask/webui/static/dist/assets/index-7129cfe1.css +0 -1
- jettask/webui/static/dist/assets/index-8d1935cc.js +0 -774
- jettask/webui/static/dist/index.html +0 -15
- jettask/webui/static/index.html +0 -1734
- jettask/webui/static/queue.html +0 -981
- jettask/webui/static/queues.html +0 -549
- jettask/webui/static/workers.html +0 -734
- {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/WHEEL +0 -0
- {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,527 @@
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
2
|
+
import { Card, Select, Space, Spin, message, Button, Tooltip, Empty } from 'antd';
|
3
|
+
import { Line } from '@ant-design/plots';
|
4
|
+
import { G2 } from "@ant-design/plots";
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
import { ReloadOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
9
|
+
import { useLoading } from '../contexts/LoadingContext';
|
10
|
+
import { useNamespace } from '../contexts/NamespaceContext';
|
11
|
+
import QueueDetailsTableV2 from '../components/QueueDetailsTableV2';
|
12
|
+
import TimeRangeSelector from '../components/TimeRangeSelector';
|
13
|
+
import { getUserPreferences, setPreference, PREFERENCE_KEYS } from '../utils/userPreferences';
|
14
|
+
|
15
|
+
import dayjs from 'dayjs';
|
16
|
+
import axios from 'axios';
|
17
|
+
|
18
|
+
const { ChartEvent } = G2;
|
19
|
+
|
20
|
+
function QueueMonitor() {
|
21
|
+
const { setLoading: setGlobalLoading } = useLoading();
|
22
|
+
const { currentNamespace } = useNamespace();
|
23
|
+
const [loading, setLoading] = useState(false);
|
24
|
+
const [queues, setQueues] = useState([]);
|
25
|
+
|
26
|
+
// 从本地存储恢复用户设置
|
27
|
+
const preferences = getUserPreferences();
|
28
|
+
const [selectedQueues, setSelectedQueues] = useState(
|
29
|
+
preferences[PREFERENCE_KEYS.QUEUE_MONITOR_SELECTED_QUEUES] || []
|
30
|
+
);
|
31
|
+
const savedTimeRange = preferences[PREFERENCE_KEYS.QUEUE_MONITOR_TIME_RANGE] || '15m';
|
32
|
+
const [timeRange, setTimeRange] = useState(savedTimeRange);
|
33
|
+
|
34
|
+
// 只有当保存的时间范围是 'custom' 时,才恢复自定义时间范围
|
35
|
+
const savedCustomRange = preferences[PREFERENCE_KEYS.QUEUE_MONITOR_CUSTOM_TIME_RANGE];
|
36
|
+
const [customTimeRange, setCustomTimeRange] = useState(
|
37
|
+
savedTimeRange === 'custom' && savedCustomRange
|
38
|
+
? [dayjs(savedCustomRange[0]), dayjs(savedCustomRange[1])]
|
39
|
+
: null
|
40
|
+
);
|
41
|
+
const [chartData, setChartData] = useState([]);
|
42
|
+
const [granularity, setGranularity] = useState('');
|
43
|
+
const [sliderValues, setSliderValues] = useState([0, 1]); // slider控件的值范围
|
44
|
+
|
45
|
+
// 用于防抖的 ref
|
46
|
+
const fetchTimeoutRef = useRef(null);
|
47
|
+
const isBrushingRef = useRef(false);
|
48
|
+
const queueDetailsRef = useRef(null);
|
49
|
+
|
50
|
+
// 使用 ref 追踪最新的 timeRange 和 customTimeRange
|
51
|
+
const timeRangeRef = useRef(timeRange);
|
52
|
+
const customTimeRangeRef = useRef(customTimeRange);
|
53
|
+
|
54
|
+
// 每次状态更新时同步更新 ref
|
55
|
+
useEffect(() => {
|
56
|
+
timeRangeRef.current = timeRange;
|
57
|
+
console.log('[QueueMonitor] timeRangeRef 更新为:', timeRange);
|
58
|
+
}, [timeRange]);
|
59
|
+
|
60
|
+
useEffect(() => {
|
61
|
+
customTimeRangeRef.current = customTimeRange;
|
62
|
+
}, [customTimeRange]);
|
63
|
+
|
64
|
+
// 获取队列列表
|
65
|
+
const fetchQueues = async () => {
|
66
|
+
if (!currentNamespace) {
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
|
70
|
+
try {
|
71
|
+
// 先调用 get_queues 获取指定命名空间的队列名称
|
72
|
+
const response = await axios.get(`/api/queues/${currentNamespace}`);
|
73
|
+
if (response.data && response.data.success) {
|
74
|
+
const queueList = response.data.data;
|
75
|
+
setQueues(queueList);
|
76
|
+
|
77
|
+
// 如果有保存的选择,恢复之前的选择;否则默认选择前10个
|
78
|
+
const savedQueues = preferences[PREFERENCE_KEYS.QUEUE_MONITOR_SELECTED_QUEUES];
|
79
|
+
if (savedQueues && savedQueues.length > 0) {
|
80
|
+
// 过滤掉不存在的队列
|
81
|
+
const validQueues = savedQueues.filter(q => queueList.includes(q));
|
82
|
+
if (validQueues.length > 0) {
|
83
|
+
setSelectedQueues(validQueues);
|
84
|
+
} else if (queueList.length > 0) {
|
85
|
+
// 如果保存的队列都不存在了,选择前10个
|
86
|
+
const defaultQueues = queueList.slice(0, 10);
|
87
|
+
setSelectedQueues(defaultQueues);
|
88
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_SELECTED_QUEUES, defaultQueues);
|
89
|
+
}
|
90
|
+
} else if (selectedQueues.length === 0 && queueList.length > 0) {
|
91
|
+
// 第一次使用,默认选择前10个
|
92
|
+
const defaultQueues = queueList.slice(0, 10);
|
93
|
+
setSelectedQueues(defaultQueues);
|
94
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_SELECTED_QUEUES, defaultQueues);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
} catch (error) {
|
98
|
+
message.error('获取队列列表失败');
|
99
|
+
console.error('Failed to fetch queues:', error);
|
100
|
+
}
|
101
|
+
};
|
102
|
+
|
103
|
+
// 获取队列趋势数据
|
104
|
+
const fetchQueueTimeline = async () => {
|
105
|
+
if (!currentNamespace || selectedQueues.length === 0) {
|
106
|
+
return;
|
107
|
+
}
|
108
|
+
|
109
|
+
setLoading(true);
|
110
|
+
setGlobalLoading(true, '加载数据中...');
|
111
|
+
try {
|
112
|
+
// 使用 ref 中的最新值
|
113
|
+
const currentTimeRange = timeRangeRef.current;
|
114
|
+
const currentCustomTimeRange = customTimeRangeRef.current;
|
115
|
+
|
116
|
+
const params = {
|
117
|
+
namespace: currentNamespace,
|
118
|
+
queues: selectedQueues,
|
119
|
+
time_range: currentTimeRange,
|
120
|
+
};
|
121
|
+
|
122
|
+
// 如果有自定义时间范围
|
123
|
+
if (currentCustomTimeRange) {
|
124
|
+
params.start_time = currentCustomTimeRange[0].toISOString();
|
125
|
+
params.end_time = currentCustomTimeRange[1].toISOString();
|
126
|
+
}
|
127
|
+
console.log('[QueueMonitor.fetchQueueTimeline] 请求参数:', params, 'currentTimeRange:', currentTimeRange, 'timeRange状态值:', timeRange);
|
128
|
+
|
129
|
+
const response = await axios.post(`/api/data/queue-timeline/${currentNamespace}`, params);
|
130
|
+
const { data, granularity: dataGranularity } = response.data;
|
131
|
+
|
132
|
+
setChartData(data);
|
133
|
+
setGranularity(dataGranularity);
|
134
|
+
// 如果是刷选触发,获取新数据后重置 slider 为全范围
|
135
|
+
// 这样新数据会完整显示
|
136
|
+
setSliderValues([0, 1]);
|
137
|
+
} catch (error) {
|
138
|
+
message.error('获取队列趋势数据失败');
|
139
|
+
console.error('Failed to fetch queue timeline:', error);
|
140
|
+
} finally {
|
141
|
+
setLoading(false);
|
142
|
+
setGlobalLoading(false);
|
143
|
+
isBrushingRef.current = false;
|
144
|
+
}
|
145
|
+
};
|
146
|
+
|
147
|
+
// 初始化和命名空间改变时获取队列
|
148
|
+
useEffect(() => {
|
149
|
+
if (currentNamespace) {
|
150
|
+
fetchQueues();
|
151
|
+
}
|
152
|
+
}, [currentNamespace]);
|
153
|
+
|
154
|
+
// 当选择的队列或时间范围改变时,重新获取数据
|
155
|
+
useEffect(() => {
|
156
|
+
if (selectedQueues.length > 0) {
|
157
|
+
console.log('触发数据更新 - timeRange:', timeRange, 'customTimeRange:', customTimeRange);
|
158
|
+
|
159
|
+
// 清除之前的定时器
|
160
|
+
if (fetchTimeoutRef.current) {
|
161
|
+
clearTimeout(fetchTimeoutRef.current);
|
162
|
+
}
|
163
|
+
|
164
|
+
// 如果是刷选触发的自定义时间范围,延迟一点获取数据
|
165
|
+
const delay = isBrushingRef.current ? 300 : 0;
|
166
|
+
|
167
|
+
fetchTimeoutRef.current = setTimeout(() => {
|
168
|
+
fetchQueueTimeline();
|
169
|
+
}, delay);
|
170
|
+
}
|
171
|
+
|
172
|
+
// 清理函数
|
173
|
+
return () => {
|
174
|
+
if (fetchTimeoutRef.current) {
|
175
|
+
clearTimeout(fetchTimeoutRef.current);
|
176
|
+
}
|
177
|
+
};
|
178
|
+
}, [selectedQueues, timeRange, customTimeRange]);
|
179
|
+
|
180
|
+
|
181
|
+
// 手动刷新
|
182
|
+
const handleRefresh = () => {
|
183
|
+
console.log('[QueueMonitor] 刷新时的timeRange:', timeRange, 'customTimeRange:', customTimeRange);
|
184
|
+
console.log('[QueueMonitor] 刷新时的selectedQueues:', selectedQueues);
|
185
|
+
fetchQueueTimeline();
|
186
|
+
// 同时刷新队列详细信息表格
|
187
|
+
if (queueDetailsRef.current) {
|
188
|
+
queueDetailsRef.current.refresh();
|
189
|
+
}
|
190
|
+
};
|
191
|
+
|
192
|
+
|
193
|
+
// 图表配置
|
194
|
+
const chartConfig = {
|
195
|
+
data: chartData,
|
196
|
+
xField: (d) => new Date(d.time),
|
197
|
+
yField: 'value',
|
198
|
+
colorField: 'queue', // 使用 colorField 替代 seriesField
|
199
|
+
smooth: true,
|
200
|
+
// 禁用动画以避免错误
|
201
|
+
animate: false,
|
202
|
+
meta: {
|
203
|
+
value: {
|
204
|
+
alias: '任务数',
|
205
|
+
},
|
206
|
+
time: {
|
207
|
+
type: 'time',
|
208
|
+
alias: '时间',
|
209
|
+
},
|
210
|
+
},
|
211
|
+
scale: {
|
212
|
+
time: {
|
213
|
+
type: 'time',
|
214
|
+
},
|
215
|
+
y: { nice: true },
|
216
|
+
// 定义不同队列的颜色
|
217
|
+
color: {
|
218
|
+
range: ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#E8684A', '#6DC8EC', '#9270CA', '#FF9D4D', '#269A99', '#FF99C3']
|
219
|
+
}
|
220
|
+
},
|
221
|
+
// point: {
|
222
|
+
// size: 3,
|
223
|
+
// shape: 'circle',
|
224
|
+
// },
|
225
|
+
style: {
|
226
|
+
lineWidth: 2,
|
227
|
+
},
|
228
|
+
xAxis: {
|
229
|
+
type: 'time',
|
230
|
+
label: {
|
231
|
+
autoRotate: true,
|
232
|
+
formatter: (text) => {
|
233
|
+
// text 可能是时间戳或ISO字符串,统一处理
|
234
|
+
const date = dayjs(text);
|
235
|
+
|
236
|
+
// 根据后端返回的粒度决定显示格式
|
237
|
+
switch (granularity) {
|
238
|
+
case 'second':
|
239
|
+
// 秒级:显示时分秒
|
240
|
+
return date.format('HH:mm:ss');
|
241
|
+
|
242
|
+
case 'minute':
|
243
|
+
// 分钟级:显示时分
|
244
|
+
return date.format('HH:mm');
|
245
|
+
|
246
|
+
case 'hour':
|
247
|
+
// 小时级:显示日期和小时
|
248
|
+
return date.format('MM-DD HH:00');
|
249
|
+
|
250
|
+
case 'day':
|
251
|
+
// 跨天:显示年月日
|
252
|
+
return date.format('YYYY-MM-DD');
|
253
|
+
|
254
|
+
default:
|
255
|
+
// 默认显示日期和小时
|
256
|
+
return date.format('MM-DD HH:mm');
|
257
|
+
}
|
258
|
+
},
|
259
|
+
},
|
260
|
+
},
|
261
|
+
yAxis: {
|
262
|
+
label: {
|
263
|
+
formatter: (v) => `${v}`,
|
264
|
+
},
|
265
|
+
title: {
|
266
|
+
text: '处理任务数',
|
267
|
+
style: {
|
268
|
+
fontSize: 14,
|
269
|
+
},
|
270
|
+
},
|
271
|
+
},
|
272
|
+
autoFit: true,
|
273
|
+
interaction: {
|
274
|
+
brushXFilter: true // 启用横向筛选
|
275
|
+
},
|
276
|
+
connectNulls: {
|
277
|
+
connect: true,
|
278
|
+
connectStroke: '#aaa',
|
279
|
+
},
|
280
|
+
legend: {
|
281
|
+
position: 'top',
|
282
|
+
itemName: {
|
283
|
+
style: {
|
284
|
+
fontSize: 12,
|
285
|
+
},
|
286
|
+
},
|
287
|
+
},
|
288
|
+
// 配置 tooltip 时间格式,使用24小时制
|
289
|
+
tooltip: {
|
290
|
+
title: (title) => {
|
291
|
+
// 使用相同的时间格式化逻辑
|
292
|
+
const date = dayjs(title.time);
|
293
|
+
switch (granularity) {
|
294
|
+
case 'second':
|
295
|
+
return date.format('YYYY-MM-DD HH:mm:ss');
|
296
|
+
case 'minute':
|
297
|
+
return date.format('YYYY-MM-DD HH:mm:ss');
|
298
|
+
case 'hour':
|
299
|
+
return date.format('YYYY-MM-DD HH:mm');
|
300
|
+
case 'day':
|
301
|
+
return date.format('YYYY-MM-DD');
|
302
|
+
default:
|
303
|
+
return date.format('YYYY-MM-DD HH:mm');
|
304
|
+
}
|
305
|
+
}
|
306
|
+
},
|
307
|
+
style: {
|
308
|
+
lineWidth: 2,
|
309
|
+
},
|
310
|
+
// 监听brush事件,实现框选后自动请求数据
|
311
|
+
onReady: (plot) => {
|
312
|
+
console.log('图表已准备就绪', plot);
|
313
|
+
|
314
|
+
// 获取所有可用的事件
|
315
|
+
const chart = plot.chart;
|
316
|
+
|
317
|
+
chart.on("brush:filter", (e) => {
|
318
|
+
console.log('Brush filter 事件:', e);
|
319
|
+
|
320
|
+
// 获取刷选的数据范围
|
321
|
+
if (e.data && e.data.selection) {
|
322
|
+
const selection = e.data.selection;
|
323
|
+
console.log('Selection 数据:', selection);
|
324
|
+
|
325
|
+
// selection[0] 是选中的时间数组
|
326
|
+
if (selection && selection[0] && selection[0].length > 0) {
|
327
|
+
const selectedTimes = selection[0];
|
328
|
+
|
329
|
+
// 获取选中时间的起止
|
330
|
+
const startTime = dayjs(selectedTimes[0]);
|
331
|
+
const endTime = dayjs(selectedTimes[selectedTimes.length - 1]);
|
332
|
+
|
333
|
+
console.log('刷选范围:', startTime.format(), endTime.format());
|
334
|
+
|
335
|
+
// 设置刷选标志
|
336
|
+
isBrushingRef.current = true;
|
337
|
+
|
338
|
+
// 更新UI状态,这会触发 useEffect 重新获取数据
|
339
|
+
setTimeRange('custom');
|
340
|
+
setCustomTimeRange([startTime, endTime]);
|
341
|
+
|
342
|
+
// 保存刷选的时间范围到本地存储
|
343
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_CUSTOM_TIME_RANGE,
|
344
|
+
[startTime.toISOString(), endTime.toISOString()]
|
345
|
+
);
|
346
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_TIME_RANGE, 'custom');
|
347
|
+
|
348
|
+
// 不需要手动更新 slider,fetchQueueTimeline 会处理
|
349
|
+
}
|
350
|
+
}
|
351
|
+
});
|
352
|
+
},
|
353
|
+
};
|
354
|
+
|
355
|
+
// 如果没有选择命名空间,显示提示
|
356
|
+
if (!currentNamespace) {
|
357
|
+
return (
|
358
|
+
<Card style={{ minHeight: '400px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
359
|
+
<Empty
|
360
|
+
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
361
|
+
description={
|
362
|
+
<span>
|
363
|
+
请先在右上角选择一个命名空间
|
364
|
+
<br />
|
365
|
+
<span style={{ color: '#999', fontSize: '12px' }}>
|
366
|
+
选择命名空间后才能查看该空间的队列数据
|
367
|
+
</span>
|
368
|
+
</span>
|
369
|
+
}
|
370
|
+
/>
|
371
|
+
</Card>
|
372
|
+
);
|
373
|
+
}
|
374
|
+
|
375
|
+
return (
|
376
|
+
<Card>
|
377
|
+
{/* 控制面板 */}
|
378
|
+
<Space size="large" style={{ marginBottom: '24px' }} wrap>
|
379
|
+
<div>
|
380
|
+
<span style={{ marginRight: '8px' }}>选择队列:</span>
|
381
|
+
<Select
|
382
|
+
mode="multiple"
|
383
|
+
style={{ width: 400 }}
|
384
|
+
placeholder="请选择队列"
|
385
|
+
value={selectedQueues}
|
386
|
+
onChange={(value) => {
|
387
|
+
setSelectedQueues(value);
|
388
|
+
// 保存到本地存储
|
389
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_SELECTED_QUEUES, value);
|
390
|
+
}}
|
391
|
+
options={queues.map(q => ({ label: q, value: q }))}
|
392
|
+
maxTagCount="responsive"
|
393
|
+
/>
|
394
|
+
</div>
|
395
|
+
|
396
|
+
<div>
|
397
|
+
<span style={{ marginRight: '8px' }}>时间范围:</span>
|
398
|
+
<TimeRangeSelector
|
399
|
+
value={timeRange}
|
400
|
+
onChange={(value) => {
|
401
|
+
console.log('[QueueMonitor] TimeRangeSelector onChange - 新值:', value, '旧值:', timeRange);
|
402
|
+
setTimeRange(value);
|
403
|
+
if (value !== 'custom') {
|
404
|
+
setCustomTimeRange(null);
|
405
|
+
// 保存到本地存储
|
406
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_TIME_RANGE, value);
|
407
|
+
console.log('[QueueMonitor] 已保存timeRange到localStorage:', value);
|
408
|
+
// 清除自定义时间范围的记忆
|
409
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_CUSTOM_TIME_RANGE, null);
|
410
|
+
// 验证保存是否成功
|
411
|
+
setTimeout(() => {
|
412
|
+
const saved = getUserPreferences();
|
413
|
+
console.log('[QueueMonitor] 验证localStorage保存:', saved[PREFERENCE_KEYS.QUEUE_MONITOR_TIME_RANGE]);
|
414
|
+
}, 100);
|
415
|
+
}
|
416
|
+
}}
|
417
|
+
customValue={customTimeRange}
|
418
|
+
onCustomChange={(dates) => {
|
419
|
+
console.log('[QueueMonitor] onCustomChange 被调用, dates:', dates);
|
420
|
+
if (dates && dates.length === 2) {
|
421
|
+
setCustomTimeRange(dates);
|
422
|
+
setTimeRange('custom');
|
423
|
+
// 保存自定义时间范围到本地存储
|
424
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_CUSTOM_TIME_RANGE,
|
425
|
+
[dates[0].toISOString(), dates[1].toISOString()]
|
426
|
+
);
|
427
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_TIME_RANGE, 'custom');
|
428
|
+
} else if (dates === null) {
|
429
|
+
// dates 为 null 时,说明是选择了预设时间,不需要做任何操作
|
430
|
+
// TimeRangeSelector 的 onChange 已经处理了
|
431
|
+
console.log('[QueueMonitor] onCustomChange - dates为null,跳过处理(预设时间已在onChange中处理)');
|
432
|
+
setCustomTimeRange(null);
|
433
|
+
} else {
|
434
|
+
// 其他情况(可能是清空了选择)
|
435
|
+
setCustomTimeRange(null);
|
436
|
+
setTimeRange('15m');
|
437
|
+
// 清除自定义时间范围
|
438
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_CUSTOM_TIME_RANGE, null);
|
439
|
+
setPreference(PREFERENCE_KEYS.QUEUE_MONITOR_TIME_RANGE, '15m');
|
440
|
+
}
|
441
|
+
}}
|
442
|
+
/>
|
443
|
+
</div>
|
444
|
+
|
445
|
+
<Button
|
446
|
+
icon={<ReloadOutlined spin={loading} />}
|
447
|
+
onClick={handleRefresh}
|
448
|
+
disabled={loading}
|
449
|
+
type="primary"
|
450
|
+
>
|
451
|
+
{loading ? '刷新' : '刷新'}
|
452
|
+
</Button>
|
453
|
+
|
454
|
+
<Tooltip
|
455
|
+
placement="bottomRight"
|
456
|
+
title={
|
457
|
+
<div style={{ fontSize: '12px' }}>
|
458
|
+
<div style={{ marginBottom: '4px', fontWeight: 'bold' }}>操作提示:</div>
|
459
|
+
<ul style={{ margin: '0 0 0 16px', paddingLeft: '0' }}>
|
460
|
+
<li>鼠标按住左键横向拖动可刷选时间范围</li>
|
461
|
+
<li>刷选后自动获取该时段的详细数据</li>
|
462
|
+
<li>数据粒度根据时间范围自动调整</li>
|
463
|
+
<li>点击图表空白处取消刷选</li>
|
464
|
+
</ul>
|
465
|
+
</div>
|
466
|
+
}
|
467
|
+
>
|
468
|
+
<QuestionCircleOutlined style={{ fontSize: '16px', color: '#1890ff', cursor: 'help' }} />
|
469
|
+
</Tooltip>
|
470
|
+
</Space>
|
471
|
+
|
472
|
+
{/* 图表 */}
|
473
|
+
<div style={{ height: '240px', position: 'relative' }}>
|
474
|
+
{chartData.length > 0 ? (
|
475
|
+
<>
|
476
|
+
<Line {...chartConfig} />
|
477
|
+
{/* {loading && (
|
478
|
+
<div style={{
|
479
|
+
position: 'absolute',
|
480
|
+
top: 0,
|
481
|
+
left: 0,
|
482
|
+
right: 0,
|
483
|
+
bottom: 0,
|
484
|
+
background: 'rgba(255, 255, 255, 0.8)',
|
485
|
+
zIndex: 1000,
|
486
|
+
display: 'flex',
|
487
|
+
alignItems: 'center',
|
488
|
+
justifyContent: 'center'
|
489
|
+
}}>
|
490
|
+
<Spin size="large" tip="正在获取选定时间范围的详细数据..." />
|
491
|
+
</div>
|
492
|
+
)} */}
|
493
|
+
</>
|
494
|
+
) : (
|
495
|
+
<div style={{
|
496
|
+
height: '100%',
|
497
|
+
display: 'flex',
|
498
|
+
alignItems: 'center',
|
499
|
+
justifyContent: 'center',
|
500
|
+
color: '#999'
|
501
|
+
}}>
|
502
|
+
{loading ? (
|
503
|
+
<Spin size="large" tip="正在加载数据..." />
|
504
|
+
) : (
|
505
|
+
selectedQueues.length === 0 ? '请选择队列' : '暂无数据'
|
506
|
+
)}
|
507
|
+
</div>
|
508
|
+
)}
|
509
|
+
</div>
|
510
|
+
|
511
|
+
{/* 分隔线 */}
|
512
|
+
{/* <Divider style={{ margin: '24px 0 16px' }} /> */}
|
513
|
+
|
514
|
+
{/* 队列详细信息表格 */}
|
515
|
+
<QueueDetailsTableV2
|
516
|
+
ref={queueDetailsRef}
|
517
|
+
autoRefresh={false}
|
518
|
+
refreshInterval={10000}
|
519
|
+
selectedQueues={selectedQueues}
|
520
|
+
timeRange={timeRange}
|
521
|
+
customTimeRange={customTimeRange}
|
522
|
+
/>
|
523
|
+
</Card>
|
524
|
+
);
|
525
|
+
}
|
526
|
+
|
527
|
+
export default QueueMonitor;
|